我有一个例子:在parent
表中有一个名为id
的列,作为外键被child
表引用。
当删除一个子行时,如何删除其父级行,如果它没有被其他子行引用?
我有一个例子:在parent
表中有一个名为id
的列,作为外键被child
表引用。
当删除一个子行时,如何删除其父级行,如果它没有被其他子行引用?
WITH del_child AS (
DELETE FROM child
WHERE child_id = 1
RETURNING parent_id, child_id
)
DELETE FROM parent p
USING del_child x
WHERE p.parent_id = x.parent_id
AND NOT EXISTS (
SELECT FROM child c
WHERE c.parent_id = x.parent_id
AND c.child_id <> x.child_id -- !
);
无论如何,子项都会被删除。我引用手册中的内容:
WITH
中的数据修改语句只执行一次,并且始终完整执行,无论主查询是否读取它们的全部(或任何)输出。请注意,这与WITH
中的SELECT
的规则不同:如前一节所述,SELECT
的执行仅限于主查询需要其输出的程度。
只有当父项没有其他子项时才会被删除。
请注意最后一个条件。与人们可能期望的相反,这是必要的,因为:
WITH
中的子语句与主查询以及彼此并发执行。因此,在使用数据修改语句时,指定更新实际发生的顺序是不可预测的。所有语句都使用相同的快照(参见第13章),因此它们无法“看到”目标表上彼此的影响。
加粗部分为我添加的强调。
我使用了列名parent_id
来替代不具描述性的id
。
为了完全消除可能的竞态条件,首先锁定父行。所有类似的操作必须遵循相同的步骤才能使其正常工作。
WITH lock_parent AS (
SELECT p.parent_id, c.child_id
FROM child c
JOIN parent p ON p.parent_id = c.parent_id
WHERE c.child_id = 12 -- provide child_id here once
FOR NO KEY UPDATE -- locks parent row.
)
, del_child AS (
DELETE FROM child c
USING lock_parent l
WHERE c.child_id = l.child_id
)
DELETE FROM parent p
USING lock_parent l
WHERE p.parent_id = l.parent_id
AND NOT EXISTS (
SELECT FROM child c
WHERE c.parent_id = l.parent_id
AND c.child_id <> l.child_id -- !
);
FOR NO KEY UPDATE
。)FOR NO KEY UPDATE
是在Postgres 9.4中引入的。在旧版本中,请改用更强的锁FOR UPDATE
。delete from child
where parent_id = 1
delete from parent
where
id = 1
and not exists (
select 1 from child where parent_id = 1
)
not exists
条件将确保只有在子项中不存在时才会删除。您可以将两个删除命令都包装在一个事务中:begin;
first_delete;
second_delete;
commit;
INSERT
一个子项。更新:如果“INSERT”先提交,则父项上的“DELETE”将不会进行。如果“DELETE”先提交,我认为“INSERT”将最终出现外键违规并被回滚。虽然时间很短,但这种情况不太可能发生。 - Erwin Brandstetter