CTE 删除未提交,直到以下语句完成

CTE delete not committed until following statements complete

我遇到的问题是已删除的数据稍后仍会出现在同一查询中。自然地,在一个完全独立的查询中,删除的数据不会出现。

这不是我的用例,但我认为这是显示问题的最简单方法:

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;

我已经把它放入 fiddle here.

似乎 DELETE 直到整个查询完成才提交,这感觉很奇怪,因为优先级要求 DELETESELECT 之前出现。

有什么方法可以在单个查询中实现吗?


编辑

答案已经回答了直接的问题。潜在的问题是删除员工然后删除其关联公司,如果没有更多员工与该公司关联。

这是我可以解决问题的查询:

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;

并更新了 fiddle

您可以看到公司没有被删除。

这是预期的并记录在案。

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

(强调我的)


可以使用链式 CTE 删除公司:

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) 

not exists 子查询中排除刚刚删除的员工很重要,因为它在第二个删除语句中始终可见,因此不存在永远不会为真。所以子查询本质上是检查是否有分配给同一公司的除已删除员工以外的员工。

在线示例:https://rextester.com/IVZ78695

你快到了!

试试这个:

DELETE FROM employee WHERE id = 1 RETURNING *;

编辑:您不需要 CTE,只需带有 RETURNING * 的 DELETE 语句即可。 :-)

您的查询需要从 CTE 中选择。
这应该给出与@johey answer 相同的结果。

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

我已经设法解决了我在问题编辑中描述的确切问题:

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;

已更新 fiddle

它本质上只是一个 hack,指示第二个 DELETE 仅在原始快照中只有一名员工与受影响的公司相关联时才对受影响的公司采取行动。

虽然很恶心,但我不会使用它。