I PostgreSQL 9.1 eller nyere du kan gøre dette med en enkelt erklæring ved hjælp af en datamodificerende CTE . Dette er generelt mindre fejltilbøjeligt. Det minimerer tidsrammen mellem de to DELETEs, hvor et løbsforhold kunne føre til overraskende resultater med samtidige 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 Fiddle.
Barnet slettes under alle omstændigheder. Jeg citerer manualen:
Datamodificerende udsagn i WITH
udføres nøjagtigt én gang ogaltid til afslutning , uafhængigt af om den primære forespørgsel læser alle (eller faktisk nogen) af deres output. Bemærk, at dette er forskelligt fra reglen for SELECT
i WITH
:som angivet i det foregående afsnit, udførelse af en SELECT
udføres kun så langt som den primære forespørgsel kræver sit output.
Forælderen slettes kun, hvis den ikke har nogen andre børn.
Bemærk den sidste betingelse. I modsætning til hvad man kunne forvente, er dette nødvendigt, da:
Underudsagn i WITH
udføres samtidigt med hinanden og med hovedforespørgslen. Derfor, når du bruger data-modificerende udsagn i WITH
, den rækkefølge, som de angivne opdateringer faktisk sker i, er uforudsigelig. Alle udsagn udføres med det samme øjebliksbillede (se kapitel 13), så de kan ikke "se" hinandens effekt på måltabellerne.
Fed fremhævelse min.
Jeg brugte kolonnenavnet parent_id
i stedet for det ikke-beskrivende id
.
Eliminér løbstilstand
For at eliminere mulige løbsforhold nævnte jeg fuldstændigt ovenfor , lås den overordnede række først . Selvfølgelig alle lignende operationer skal følge samme procedure for at få det til at fungere.
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å denne måde kun én transaktion ad gangen kan låse den samme forælder. Så det kan ikke ske, at flere transaktioner sletter børn af samme forælder, stadig ser andre børn og skåner forælderen, mens alle børn er væk bagefter. (Opdateringer på ikke-nøglekolonner er stadig tilladt med FOR NO KEY UPDATE
.)
Hvis sådanne tilfælde aldrig opstår, eller du kan leve med, at det (næsten aldrig) sker - er den første forespørgsel billigere. Ellers er dette den sikre vej.
FOR NO KEY UPDATE
blev introduceret med Postgres 9.4. Detaljer i manualen. I ældre versioner, brug den stærkere lås FOR UPDATE
i stedet.