In PostgreSQL 9.1 oder höher Sie können dies mit einer einzigen Anweisung unter Verwendung eines datenmodifizierenden CTE tun . Dies ist im Allgemeinen weniger fehleranfällig. Es minimiert der Zeitrahmen zwischen den beiden DELETES, in denen ein Rennzustand vorliegt könnte bei gleichzeitigen Operationen zu überraschenden Ergebnissen führen:
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-Geige.
Das Kind wird in jedem Fall gelöscht. Ich zitiere das Handbuch:
Datenmodifizierende Anweisungen in WITH
werden genau einmal und immer vollständig ausgeführt , unabhängig davon, ob die primäre Abfrage alle (oder tatsächlich alle) ihrer Ausgabe liest. Beachten Sie, dass dies von der Regel für SELECT
abweicht in WITH
:wie im vorherigen Abschnitt angegeben, Ausführung eines SELECT
wird nur so weit getragen, wie die primäre Abfrage ihre Ausgabe erfordert.
Der Elternteil wird nur gelöscht, wenn er keinen anderen hat Kinder.
Beachten Sie die letzte Bedingung. Anders als man erwarten könnte, ist dies notwendig, denn:
Die Unteranweisungen in WITH
werden gleichzeitig ausgeführt miteinander und mit der Hauptabfrage. Daher bei Verwendung von datenmodifizierenden Anweisungen in WITH
, ist die Reihenfolge, in der die angegebenen Aktualisierungen tatsächlich erfolgen, unvorhersehbar. Alle Anweisungen werden mit demselben Snapshot ausgeführt (siehe Kapitel 13), sodass sie die Auswirkungen der anderen auf die Zieltabellen nicht „sehen“ können.
Fettdruck von mir.
Ich habe den Spaltennamen parent_id
verwendet anstelle der nicht beschreibenden id
.
Rennbedingung beseitigen
Um mögliche Race-Conditions zu eliminieren habe ich oben komplett erwähnt , sperren Sie zuerst die übergeordnete Zeile . Natürlich alle ähnliche Operationen müssen dem gleichen Verfahren folgen, damit es funktioniert.
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 -- !
);
Auf diese Weise nur eine Transaktion zu einem Zeitpunkt kann denselben Elternteil sperren. Es kann also nicht passieren, dass mehrere Transaktionen Kinder desselben Elternteils löschen, andere Kinder noch sehen und den Elternteil verschonen, während danach alle Kinder weg sind. (Aktualisierungen von Nicht-Schlüsselspalten sind weiterhin mit FOR NO KEY UPDATE
erlaubt .)
Wenn solche Fälle nie vorkommen oder Sie damit leben können (fast nie), ist die erste Abfrage billiger. Andernfalls ist dies der sichere Pfad.
FOR NO KEY UPDATE
wurde mit Postgres 9.4 eingeführt. Details im Handbuch. Verwenden Sie in älteren Versionen die stärkere Sperre FOR UPDATE
stattdessen.