I PostgreSQL 9.1 eller senare du kan göra detta med ett enda uttalande med en datamodifierande CTE . Detta är i allmänhet mindre felbenäget. Det minimerar tidsramen mellan de två DELETEs där en tävlingsförhållanden kan leda till överraskande resultat med samtidiga operationer:
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-fiol.
Barnet raderas i alla fall. Jag citerar manualen:
Datamodifierande uttalanden i
WITH
exekveras exakt en gång ochalltid till slut , oberoende av om den primära frågan läser alla (eller faktiskt någon) av deras utdata. Observera att detta skiljer sig från regeln förSELECT
iWITH
:som anges i föregående avsnitt, exekvering av enSELECT
utförs endast så långt som den primära frågan kräver dess utdata.
Föräldern tas bara bort om den inte har någon annan barn.
Observera det sista villkoret. Tvärtemot vad man kan förvänta sig är detta nödvändigt, eftersom:
Undersatserna i
WITH
exekveras samtidigt med varandra och med huvudfrågan. Därför, när du använder datamodifierande uttalanden iWITH
, den ordning i vilken de angivna uppdateringarna faktiskt sker är oförutsägbar. Alla uttalanden exekveras med samma ögonblicksbild (se kapitel 13), så de kan inte "se" varandras effekter på måltabellerna.
Fet betoning min.
Jag använde kolumnnamnet parent_id
i stället för det icke-beskrivande id
.
Eliminera tävlingsvillkoret
För att eliminera möjliga tävlingsförhållanden nämnde jag ovan helt , lås den överordnade raden först . Naturligtvis alla liknande operationer måste följa samma procedur för att få det att fungera.
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 -- !
);
På så sätt bara en transaktion åt gången kan låsa samma förälder. Så det kan inte hända att flera transaktioner tar bort barn till samma förälder, fortfarande ser andra barn och skonar föräldern, medan alla barn är borta efteråt. (Uppdateringar av icke-nyckelkolumner är fortfarande tillåtna med FOR NO KEY UPDATE
.)
Om sådana fall aldrig inträffar eller om du kan leva med att det (sällan) händer – är den första frågan billigare. Annars är det här den säkra vägen.
FOR NO KEY UPDATE
introducerades med Postgres 9.4. Detaljer i manualen. I äldre versioner använd det starkare låset FOR UPDATE
istället.