CTE delete not committed until following statements complete

十年热恋 提交于 2019-12-22 16:57:48

问题


The problem I'm having is that deleted data still appears later in the same query. Naturally, in a completely separate query, the deleted data does not appear.

This isn't my use-case, but I think this it's the simplest way to show the problem:

CREATE TABLE company (id INT PRIMARY KEY, name TEXT);
CREATE TABLE employee (id INT PRIMARY KEY, company_id INT REFERENCES company(id), name TEXT);

INSERT INTO company VALUES (1, 'first company');
INSERT INTO company VALUES (2, 'second company');

INSERT INTO employee VALUES (1, 1, 'first employee');
INSERT INTO employee VALUES (2, 2, 'second employee');

-- this select can successfully query for the data which has just been deleted
WITH deleted_employee AS (DELETE FROM employee WHERE id = 1 RETURNING id)
SELECT id, name FROM employee JOIN deleted_employee USING (id);

-- this select shows it has been deleted
SELECT * FROM employee;

I've put it into a fiddle here.

It seems the DELETE just isn't committed until the whole query has completed, which feels strange since the precedence requires that the DELETE occurs before the SELECT.

Is there any way to achieve this in a single query?


Edit

The answers have answered the direct problem. The underlying problem is to delete an employee then delete its associated company, if there are no more employees associated with that company.

Here's the query I though would do the trick:

WITH affected_company AS (DELETE FROM employee WHERE id = 1 RETURNING company_id)
DELETE FROM company
USING affected_company
WHERE NOT EXISTS (
  SELECT 1
  FROM employee
  WHERE company_id = affected_company.company_id
);

SELECT * FROM company;
SELECT * FROM employee;

And an updated fiddle.

You can see the company isn't being deleted.


回答1:


This is expected and documented.

Quote from the manual

The sub-statements in WITH are executed concurrently with each other and with the main query. Therefore, when using data-modifying statements in WITH, the order in which the specified updates actually happen is unpredictable. All the statements are executed with the same snapshot (see Chapter 13), so they cannot “see” one another's effects on the target tables. This alleviates the effects of the unpredictability of the actual order of row updates, and means that RETURNING data is the only way to communicate changes between different WITH sub-statements and the main query

(emphasis mine)


You can use chained CTEs to delete the company:

with deleted_emp as (
  delete from employee 
  where id = 1 
  returning company_id, id as employee_id
)
delete from company
where id in (select company_id from deleted_emp) 
  and not exists (select * 
                  from employee e
                     join deleted_emp af 
                       on af.company_id = e.company_id 
                      and e.id <> af.employee_id) 

It's important to exclude the just deleted employee from the not exists sub-query as that will always be visible in the second delete statement and thus the not exists would never be true. So the sub-query essentially checks if there is an employee other than the deleted one assigned to the same company.

Online example: https://rextester.com/IVZ78695




回答2:


You are almost there!

Try this instead:

DELETE FROM employee WHERE id = 1 RETURNING *;

Edit: you don't need the CTE, just the DELETE statement with RETURNING * does the trick. :-)




回答3:


Your query needs to be selecting from the CTE.
This should give the same results as @johey answer.

WITH
  deleted_employee AS (
    DELETE FROM
      employee
     WHERE
       id = 1
     RETURNING
       *
  )
SELECT
  *
FROM
 deleted_employee



回答4:


I've managed to solve the exact problem I described in the edit to my question:

WITH affected_company AS (DELETE FROM employee WHERE id = 1 RETURNING company_id)
DELETE FROM company WHERE id IN (
  SELECT company_id
  FROM employee
  JOIN affected_company USING(company_id)
  GROUP BY company_id
  HAVING COUNT(*) = 1
);

SELECT * FROM company;
SELECT * FROM employee;

Updated fiddle.

It's essentially just a hack instructing the second DELETE only to act on the affected company if there was exactly one employee associated with that company at the original snapshot.

It's gross though and I won't be using it.



来源:https://stackoverflow.com/questions/52757679/cte-delete-not-committed-until-following-statements-complete

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!