PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Supprimer le parent s'il n'est référencé par aucun autre enfant

Dans PostgreSQL 9.1 ou version ultérieure vous pouvez le faire avec une seule déclaration en utilisant un CTE de modification des données . Ceci est généralement moins sujet aux erreurs. Il minimise le laps de temps entre les deux DELETE dans lequel une conditions de concurrence pourrait conduire à des résultats surprenants avec des opérations simultanées :

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.

L'enfant est supprimé dans tous les cas. Je cite le manuel :

Déclarations de modification de données dans WITH sont exécutés exactement une fois, et toujours jusqu'à la fin , indépendamment du fait que la requête principale lit tout (ou même une partie) de leur sortie. Notez que ceci est différent de la règle pour SELECT en WITH  :comme indiqué dans la section précédente,exécution d'un SELECT n'est transporté que dans la mesure où la requête principale demande sa sortie.

Le parent n'est supprimé que s'il n'a pas d'autre enfants.
Notez la dernière condition. Contrairement à ce à quoi on pourrait s'attendre, cela est nécessaire, puisque :

Les sous-instructions dans WITH sont exécutés simultanément les uns avec les autres et avec la requête principale. Par conséquent, lors de l'utilisation d'instructions de modification de données dans WITH , l'ordre dans lequel les mises à jour spécifiées se produisent réellement est imprévisible. Toutes les instructions sont exécutées avec le même instantané (voir chapitre 13), elles ne peuvent donc pas "voir" les effets des autres sur les tables cibles.

Mise en gras de la mienne.
J'ai utilisé le nom de colonne parent_id à la place de l'id non descriptif .

Éliminer la condition de concurrence

Pour éliminer les conditions de course possibles que j'ai mentionnées ci-dessus complètement , verrouillez la ligne parent en premier . Bien sûr, tous des opérations similaires doivent suivre la même procédure pour que cela fonctionne.

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   -- !
   );

De cette façon, seulement un transaction à la fois peut verrouiller le même parent. Il ne peut donc pas arriver que plusieurs transactions suppriment les enfants du même parent, voient encore d'autres enfants et épargnent le parent, alors que tous les enfants sont partis par la suite. (Les mises à jour sur les colonnes non-clés sont toujours autorisées avec FOR NO KEY UPDATE .)

Si de tels cas ne se produisent jamais ou si vous pouvez (presque) vivre avec cela, la première requête est moins chère. Sinon, c'est le chemin sécurisé.

FOR NO KEY UPDATE a été introduit avec Postgres 9.4. Détails dans le manuel. Dans les anciennes versions, utilisez le verrou plus fort FOR UPDATE à la place.