In PostgreSQL 9.1 of hoger u kunt dit doen met een enkele instructie met behulp van een data-modifying CTE . Dit is over het algemeen minder foutgevoelig. Het minimaliseert het tijdsbestek tussen de twee DELETE's waarin een race-omstandigheden kan tot verrassende resultaten leiden bij gelijktijdige bewerkingen:
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.
Het kind wordt in ieder geval verwijderd. Ik citeer de handleiding:
Verklaringen voor het wijzigen van gegevens in
WITH
worden precies één keer uitgevoerd, enaltijd volledig , onafhankelijk van het feit of de primaire query alle (of zelfs enige) van hun uitvoer leest. Merk op dat dit anders is dan de regel voorSELECT
inWITH
:zoals vermeld in de vorige sectie,uitvoering van eenSELECT
wordt alleen uitgevoerd voor zover de primaire query de uitvoer vereist.
De ouder wordt alleen verwijderd als deze geen andere . heeft kinderen.
Let op de laatste voorwaarde. In tegenstelling tot wat men zou verwachten, is dit nodig, omdat:
De sub-statements in
WITH
worden gelijktijdig uitgevoerd met elkaar en met de hoofdvraag. Daarom, bij gebruik van data-modifyingstatements inWITH
, is de volgorde waarin de opgegeven updates daadwerkelijk plaatsvinden onvoorspelbaar. Alle instructies worden uitgevoerd met dezelfde momentopname (zie hoofdstuk 13), zodat ze elkaars effecten op de doeltabellen niet kunnen "zien".
Vetgedrukte nadruk van mij.
Ik gebruikte de kolomnaam parent_id
in plaats van de niet-beschrijvende id
.
Elimineren van raceconditie
Om mogelijke race-omstandigheden te elimineren die ik hierboven heb genoemd volledig , vergrendel de bovenliggende rij eerst . Natuurlijk, allemaal soortgelijke bewerkingen moeten dezelfde procedure volgen om het te laten werken.
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 -- !
);
Op deze manier slechts één transactie tegelijk kan dezelfde ouder vergrendelen. Het kan dus niet gebeuren dat meerdere transacties kinderen van dezelfde ouder verwijderen, nog andere kinderen zien en de ouder sparen, terwijl alle kinderen daarna weg zijn. (Updates van niet-sleutelkolommen zijn nog steeds toegestaan met FOR NO KEY UPDATE
.)
Als dergelijke gevallen zich nooit voordoen of u kunt ermee leven dat het (nauwelijks) gebeurt, is de eerste vraag goedkoper. Anders is dit het veilige pad.
FOR NO KEY UPDATE
werd geïntroduceerd met Postgres 9.4. Details in de handleiding. Gebruik in oudere versies het sterkere slot FOR UPDATE
in plaats daarvan.