如果父级未被任何其他子级引用,则删除父级

问题描述 投票:0回答:2

我有一个示例情况:

parent
表有一个名为
id
的列,在
child
表中作为外键引用。

删除子行时,如果父行没有被任何其他子行引用,如何同时删除父行?

sql postgresql foreign-keys common-table-expression referential-integrity
2个回答
16
投票

在 PostgreSQL 9.1 或更高版本中,您可以使用 数据修改 CTE 通过一条语句来完成此操作。这通常不太容易出错。它最小化两次删除之间的时间范围,其中“竞争条件”可能会导致并发操作出现令人惊讶的结果: 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 -- ! );

小提琴


sqlfiddle 无论如何,孩子都会被删除。我引用

手册

WITH

中的数据修改语句只执行一次,并且

始终完成
,与主查询是否读取无关 他们的所有(或者实际上是任何)输出。请注意,这是不同的 根据 SELECT
WITH
的规则:如上一节所述,
SELECT
的执行仅执行到主查询 需要它的输出。

仅当父级没有
other

子级时才会被删除。 请注意最后一个条件。与人们的预期相反,这是必要的,因为:

WITH

中的子语句彼此

并发
执行 以及主要查询。因此,在使用数据修改时 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
    


2
投票
在子级中删除后,在父级中执行此操作:

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;

© www.soinside.com 2019 - 2024. All rights reserved.