PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Elimina genitore se non è referenziato da nessun altro figlio

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.