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 pourSELECT
enWITH
:comme indiqué dans la section précédente,exécution d'unSELECT
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 dansWITH
, 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.