In PostgreSQL 9.1 o versioni successive puoi farlo con una singola istruzione utilizzando un CTE per la modifica dei dati . Questo è generalmente meno soggetto a errori. riduce al minimo l'intervallo di tempo tra i due DELETE in cui una condizioni di gara potrebbe portare a risultati sorprendenti con operazioni simultanee:
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.
Il bambino viene comunque cancellato. Cito il manuale:
Dichiarazioni di modifica dei dati in WITH
vengono eseguiti esattamente una volta esempre fino al completamento , indipendentemente dal fatto che la query primaria legga tutto (o addirittura qualsiasi) del loro output. Nota che questo è diverso dalla regola per SELECT
in WITH
:come indicato nella sezione precedente, esecuzione di un SELECT
viene trasportato solo fino a quando la query primaria richiede il suo output.
Il genitore viene eliminato solo se non ha altro bambini.
Nota l'ultima condizione. Contrariamente a quanto ci si potrebbe aspettare, questo è necessario, poiché:
Le sottodichiarazioni in WITH
vengono eseguiti contemporaneamente tra loro e con la query principale. Pertanto, quando si utilizzano dichiarazioni di modifica dei dati in WITH
, l'ordine in cui avvengono effettivamente gli aggiornamenti specificati è imprevedibile. Tutte le istruzioni vengono eseguite con la stessa istantanea (vedi Capitolo 13), quindi non possono "vedere" gli effetti reciproci sulle tabelle di destinazione.
Enfasi in grassetto la mia.
Ho usato il nome della colonna parent_id
al posto dell'id
non descrittivo .
Elimina la condizione di gara
Per eliminare possibili condizioni di gara ho menzionato sopra completamente , blocca la riga principale prima . Naturalmente, tutti operazioni simili devono seguire la stessa procedura per farlo funzionare.
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 -- !
);
In questo modo solo uno transazione alla volta può bloccare lo stesso genitore. Quindi non può succedere che più transazioni cancellino i figli dello stesso genitore, vedano ancora altri figli e risparmino il genitore, mentre tutti i figli scompaiono in seguito. (Gli aggiornamenti su colonne non chiave sono ancora consentiti con FOR NO KEY UPDATE
.)
Se tali casi non si verificano mai o puoi conviverci (quasi quasi mai), la prima query è più economica. Altrimenti, questo è il percorso sicuro.
FOR NO KEY UPDATE
è stato introdotto con Postgres 9.4. Dettagli nel manuale. Nelle versioni precedenti usa il blocco più forte FOR UPDATE
invece.