sql - 如果其他子项未引用父项,则删除父项

标签 sql postgresql foreign-keys common-table-expression referential-integrity

我有一个示例情况:parent 表有一个名为 id 的列,在 child 表中作为外键引用。

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

最佳答案

在 PostgreSQL 9.1 或更高版本 中,您可以通过使用 data-modifying CTE 的单个语句来完成此操作.这通常不太容易出错。它最小化两个 DELETE 之间的时间范围,在这种情况下,竞争条件可能会导致并发操作产生令人惊讶的结果:

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 1
   FROM   child c
   WHERE  c.parent_id = x.parent_id
   AND    c.child_id <> x.child_id   -- !
   );

db<> fiddle here
<子>旧sqlfiddle

无论如何都会删除 child 。我引用 the manual :

Data-modifying statements in WITH are executed exactly once, and always to completion, independently of whether the primary query reads all (or indeed any) of their output. Notice that this is different from the rule for SELECT in WITH: as stated in the previous section, execution of a SELECT is carried only as far as the primary query demands its output.

只有当父项没有其他个子项时才会被删除。
注意最后一个条件。与人们的预期相反,这是必要的,因为:

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" each others' effects on the target tables.

大胆强调我的。
我使用列名 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 1
   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 中引入的。 Details in the manual.在旧版本中,使用更强的锁 FOR UPDATE 代替。

关于sql - 如果其他子项未引用父项,则删除父项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15809463/

相关文章:

java - 查询语法异常 : with-clause not allowed on fetched associations; use filters

php - 如何获取学说中下一个table_id_seq(primary_key)的值?

python - 在 Django Rest Framework 中下拉外键选择

python - 如何根据 pandas 中的公共(public)键合并两个数据集?

MySQL - 无法添加外键约束

java - 如何使用 Hibernate/JDBC 删除函数?

php - 通过 PHP 以最快的速度将数据从 MSSQL 传输到 MySQL

如果每列等于相同的值,MySQL 会更新行

postgresql - 我如何在 ubuntu 16.04 上将我的 Postgresql 9.5 升级到 Postgresql 10

sql - 加载数据文件 + 禁用/启用按键性能