I am trying to figure out if there is a way I can make nested CTEs work for this particular case.
Consider the following (highly contrived) scenario which is based on the actual application: there's a single column table of employee id's. Then there's an employee property table with all the details. (The main reason behind the single col table is often as a matter of course new employee id's need to be created in lots and assigned before any details of the actual staff is known.)
Now to the task at hand, we are inserting details (i.e. the name) for a new employee, but first we need to check if an employee with that name already exists. If it does, we'll simply return the id, and if not, we'll create a new employee record and then insert the details, finally returning the newly created id.
To recreate this test scenario:
CREATE TABLE public.employee (
id text DEFAULT gen_random_uuid(),
PRIMARY KEY (id)
);
CREATE TABLE public.employee_details (
employee_id text,
name text,
PRIMARY KEY (employee_id),
FOREIGN KEY (employee_id) REFERENCES public.employee(id)
);
The query I am trying to hammer into shape looks like shown below.
with
e as
(select name, employee_id from employee_details where name = 'jack bauer'),
i as (insert into employee_details (name, employee_id)
select 'jack bauer',
(with a as (insert into employee values(default) RETURNING id) select a.id from a)
where not exists (select 1 from e) returning name, employee_id)
select employee_id, name from e
union all
select employee_id, name from i;
If I replace the nested CTE with an already created id (executing the nested CTE separately), it works (but can result in the creation of a superfluous id). It is also possible to simply move the nested CTE to the top level (so the whole thing will look like with e as (..), i as (..), a as (..) select .. where not exists...
, but this would also mean that a superfluous employee id is created where no details need to be inserted. I want to figure out a way to do it "inline" - so the new id will only be created if the not exists
clause returns true.
I keep getting the error:
WITH clause containing a data-modifying statement must be at the top level.
I guess the trouble is that the nested CTE returns a "column" whereas the whole query will work if it gets a "value" instead (which it does, when one simply copies in a text value instead of the CTE). I did come across a somewhat related discussion at this question mentioning an apparent bug which has been fixed since 9.3. I don't know if that is related to my troubles here. To quote from the linked discussion:
The parse analysis code seems to think that WITH can only be attached to the top level or a leaf-level SELECT within a set operation tree; but the grammar follows the SQL standard which says no such thing
I'm using Postgres 10.3.
employee_details.name
supposed to beUNIQUE
? (We know that names are often not unique.)