В 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 1
FROM child c
WHERE c.parent_id = x.parent_id
AND c.child_id <> x.child_id -- !
);
SQL Fiddle.
При всички случаи детето се изтрива. Цитирам ръководството:
Операции за модифициране на данни в
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 -- !
);
По този начин само един транзакция в даден момент може да заключи един и същ родител. Така че не може да се случи, че множество транзакции изтриват деца на един и същи родител, все още виждат други деца и щадят родителя, докато всички деца са изчезнали след това. (Актуализации на неключови колони все още са разрешени с FOR NO KEY UPDATE
.)
Ако такива случаи никога не се случват или можете да живеете с това (едва ли някога) да се случи - първата заявка е по-евтина. В противен случай това е защитеният път.
FOR NO KEY UPDATE
беше представен с Postgres 9.4. Подробности в ръководството. В по-старите версии използвайте по-силното заключване FOR UPDATE
вместо това.