我有一个示例情况:parent
表有一个名为id
的列,在child
表中作为外键引用。
删除子行时,如果父项没有被其他孩子引用,如何删除父项?
答案 0 :(得分:10)
在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 -- !
);
无论如何,孩子都会被删除。我引用the manual:
WITH
中的数据修改语句只执行一次,并且 始终完成,与主查询是否读取无关 所有(或实际上任何)他们的输出。请注意,这是不同的 来自SELECT
中WITH
的规则:如上一节所述, 只有主查询才会执行SELECT
的执行 要求它的输出。
只有在没有其他子项的情况下才会删除父项 注意最后一个条件。与人们的期望相反,这是必要的,因为:
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 1
FROM child c
WHERE c.parent_id = l.parent_id
AND c.child_id <> l.child_id -- !
);
这种方式一次只能一个事务可以锁定同一个父项。因此,不可能发生多个事务删除同一父项的子项,仍然看到其他子项并保留父项,而所有子项都在之后消失。 ({1}}仍允许对非键列进行更新。)
如果这种情况从未发生过,或者你可以忍受它(几乎从未发生过) - 第一个查询更便宜。否则,这是安全的道路。
Postgres 9.4引入了 FOR NO KEY UPDATE
。 Details in the manual.在旧版本中,请使用更强的锁FOR NO KEY UPDATE
。
答案 1 :(得分:0)
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;