Sql 删除父项(如果为');s未被任何其他子级引用
我有一个示例情况:Sql 删除父项(如果为');s未被任何其他子级引用,sql,postgresql,foreign-keys,common-table-expression,referential-integrity,Sql,Postgresql,Foreign Keys,Common Table Expression,Referential Integrity,我有一个示例情况:parent表有一个名为id的列,在child表中作为外键引用 删除子行时,如果父行未被任何其他子行引用,如何删除父行 delete from child where parent_id = 1 在子项中删除后,在父项中执行: delete from parent where id = 1 and not exists ( select 1 from child where parent_id = 1 ) 不存在条件将确保只有在子项中
parent
表有一个名为id
的列,在child
表中作为外键引用
删除子行时,如果父行未被任何其他子行引用,如何删除父行
delete from child
where parent_id = 1
在子项中删除后,在父项中执行:
delete from parent
where
id = 1
and not exists (
select 1 from child where parent_id = 1
)
不存在
条件将确保只有在子项中不存在时才会将其删除。您可以在事务中包装这两个删除命令:
begin;
first_delete;
second_delete;
commit;
在PostgreSQL9.1或更高版本中,您可以通过使用。这通常不太容易出错。它最小化了两次删除之间的时间间隔,在这两次删除之间,竞争条件可能导致并发操作的意外结果:
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 -- !
);
这样,一次只有一个事务可以锁定同一父事务。因此,不可能发生多个事务删除同一父级的子级,仍然看到其他子级并保留父级,而所有子级随后都消失的情况。(对于无键更新,仍允许使用对非键列进行更新
)
如果这种情况从未发生过,或者你可以忍受它(几乎从未发生过),那么第一个查询会更便宜。否则,这就是安全路径
Postgres 9.4引入了无密钥更新的
。在较旧的版本中,将更强的锁用于更新
。它最小化了两次删除之间的时间范围,在这两次删除之间,竞争条件可能会导致意外的结果,如果两次删除都包装在一个事务中,可能会发生并发操作?@ClodoaldoNeto:是的,可能会。沿途都会获得锁。并发事务可以在两个删除之间插入一个子项。更新:如果先提交INSERT
,则父级上的DELETE
将不会通过。如果DELETE
先提交,我认为INSERT
将以外键冲突结束并回滚。不太可能,因为时间范围很小,但有可能。事务失败是否会产生意外的结果?取决于用例和并发负载。我从来没有见过它失败过(除非我为了一个测试用例而激发它)。@Felix:因为父母只有在没有孩子的时候才会被删除,所以在这个场景中,级联FK永远不会触发。但这两种方式都应该奏效。如果仍然不清楚,请开始一个新问题。您始终可以引用此文件作为上下文。
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 -- !
);