【问题标题】:CTE delete not committed until following statements complete在以下语句完成之前未提交 CTE 删除
【发布时间】:2019-03-16 09:29:31
【问题描述】:

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

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

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;

我已经把它放入小提琴here

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

有没有办法在单个查询中实现这一点?


编辑

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

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

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

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

【问题讨论】:

  • 您到底想达到什么目的?如果您只想查看已删除的行,那么 Johey 的答案可能就是您正在寻找的。如果没有,那么您需要更详细地描述您的实际需求
  • 我最初只是想了解发生了什么,以避免被灌输解决我的确切问题的方法。我现在更新了,我理解得更好了。
  • 我明白这一点,您尝试创建一个简单的独立示例是值得称赞的(很少在问题上投入那么多精力),但在这种情况下需要更多细节;)
  • 我认为不可能通过一个查询在多个表中删除,但如果是这样,我很乐意学习如何做到这一点。也许作为一种解决方法,您可以使用两个用分号分隔的查询来做到这一点?
  • @johey 我也不认为有一个很好的解决方案。我发布了一个不太好的解决方案,它适用于我编辑中描述的确切场景,仅用于教育目的! :)

标签: sql postgresql common-table-expression sql-delete


【解决方案1】:

这是预期并记录在案的。

Quote from the manual

WITH 中的子语句彼此同时执行,并与主查询同时执行。因此,当在 WITH 中使用数据修改语句时,指定更新实际发生的顺序是不可预测的。所有语句都使用相同的快照执行(参见第 13 章),因此它们无法“看到”彼此对目标表的影响。这减轻了行更新的实际顺序的不可预测性的影响,并且意味着返回数据是在不同 WITH 子语句和主查询之间传达更改的唯一方式

(强调我的)


可以使用链式 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

【讨论】:

  • 该死,我怎么错过了。我仍然没有得到的是这个例子WITH t AS (UPDATE products SET price = price * 1.05 RETURNING *) SELECT * FROM t; 它说外部SELECT 将返回更新的数据,这不是我所看到的。如果 DELETESELECT 有依赖关系,它们如何同时运行?
  • 哦,我现在明白了!不得不把那部分读了几遍。我的问题只是询问您已清除的行为,所以谢谢。我已经用根本问题更新了我的问题。
【解决方案2】:

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

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 仅在原始快照中只有一名员工与该公司相关联时才对受影响的公司采取行动。

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      你快到了!

      试试这个:

      DELETE FROM employee WHERE id = 1 RETURNING *;
      

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

      【讨论】:

      • 我可能会误解,但这给了我同样的结果WITH deleted_employee AS (DELETE FROM employee WHERE id = 1 RETURNING *) SELECT id, employee.name FROM employee JOIN deleted_employee USING (id); :(
      • 谢谢!对于我发布的简化示例,这确实是一个更好的解决方案;然而,这更多的是我不理解的行为。我已经更新了我的问题以解释真正的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-01
      • 2012-05-07
      • 2014-07-05
      • 2014-11-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多