Dies ist eine Umsetzungsentscheidung. Es wird in der Postgres-Dokumentation unter WITH
Abfragen (allgemeine Tabellenausdrücke) beschrieben . Es gibt zwei Absätze, die sich auf das Problem beziehen.
Erstens der Grund für das beobachtete Verhalten:
Die Sub-Anweisungen in WITH
gleichzeitig ausgeführt werden miteinander und mit der Hauptabfrage . Wenn Sie datenmodifizierende Anweisungen in verwenden WITH
, ist die Reihenfolge, in der die angegebenen Aktualisierungen tatsächlich stattfinden, daher nicht vorhersehbar. Alle Anweisungen werden mit demselben Snapshot ausgeführt (siehe Kapitel 13), sodass sie die Auswirkungen des anderen auf die Zieltabellen nicht "sehen" können. Dies verringert die Auswirkungen der Unvorhersehbarkeit der tatsächlichen Reihenfolge der Zeilenaktualisierungen und bedeutet, dass RETURNING
Daten die einzige Möglichkeit sind, Änderungen zwischen verschiedenen WITH
Unteranweisungen und der Hauptabfrage zu kommunizieren . Ein Beispiel dafür ist, dass in ...
Nachdem ich einen Vorschlag zusammen gepostet habe pgsql-docs gepostet hatte, erklärte Marko Tiikkaja (was mit Erwins Antwort übereinstimmt):
Die Fälle Insert-Update und Insert-Delete funktionieren nicht, da die UPDATEs und DELETEs die INSERTed-Zeilen nicht sehen können, da ihr Snapshot vor dem INSERT erstellt wurde. An diesen beiden Fällen ist nichts Unvorhersehbares.
Der Grund, warum Ihre Aussage nicht aktualisiert wird, kann im ersten Absatz (über "Schnappschüsse") erläutert werden. Wenn Sie CTEs ändern, werden alle und die Hauptabfrage ausgeführt und "sehen" denselben Snapshot der Daten (Tabellen) wie unmittelbar vor der Ausführung der Anweisung. CTEs können mithilfe der RETURNING
Klausel Informationen darüber, was sie eingefügt / aktualisiert / gelöscht haben, aneinander und an die Hauptabfrage übergeben , aber sie können die Änderungen in den Tabellen nicht direkt sehen. Mal sehen, was in Ihrer Aussage passiert:
WITH newval AS (
INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;
Wir haben 2 Teile, den CTE ( newval
):
-- newval
INSERT INTO tbl(val) VALUES (1) RETURNING id
und die Hauptabfrage:
-- main
UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id
Der Ablauf der Ausführung ist ungefähr so:
initial data: tbl
id │ val
(empty)
/ \
/ \
/ \
newval: \
tbl (after newval) \
id │ val \
1 │ 1 |
|
newval: returns |
id |
1 |
\ |
\ |
\ |
main query
Als Ergebnis, wenn die Hauptabfrage dem beitritt tbl
(wie im Snapshot dargestellt) mit der newval
Tabelle verknüpft, wird daher eine leere Tabelle mit einer einzeiligen Tabelle verknüpft. Offensichtlich werden 0 Zeilen aktualisiert. Die Anweisung kam also nie wirklich dazu, die neu eingefügte Zeile zu ändern, und das sehen Sie.
Die Lösung in Ihrem Fall besteht darin, entweder die Anweisung neu zu schreiben, um die richtigen Werte einzufügen, oder 2 Anweisungen zu verwenden. Eine, die eingefügt und eine zweite aktualisiert werden muss.
Es gibt andere, ähnliche Situationen, als ob die Anweisung ein INSERT
und dann ein DELETE
in denselben Zeilen hätte. Das Löschen würde aus genau den gleichen Gründen fehlschlagen.
Einige andere Fälle mit Update-Update und Update-Delete sowie deren Verhalten werden in einem folgenden Absatz auf derselben Dokumentenseite erläutert.
Der Versuch, dieselbe Zeile zweimal in einer einzelnen Anweisung zu aktualisieren, wird nicht unterstützt. Es findet nur eine der Änderungen statt, aber es ist nicht einfach (und manchmal nicht möglich), zuverlässig vorherzusagen, welche. Dies gilt auch für das Löschen einer Zeile, die bereits in derselben Anweisung aktualisiert wurde: Es wird nur die Aktualisierung durchgeführt. Daher sollten Sie generell vermeiden, eine einzelne Zeile zweimal in einer einzelnen Anweisung zu ändern. Vermeiden Sie insbesondere das Schreiben von WITH-Unteranweisungen, die sich auf dieselben Zeilen auswirken können, die durch die Hauptanweisung oder eine Unteranweisung eines Geschwisters geändert wurden. Die Auswirkungen einer solchen Aussage sind nicht vorhersehbar.
Und in der Antwort von Marko Tiikkaja:
Die Fälle Update-Update und Update-Delete werden explizit nicht durch dieselben zugrunde liegenden Implementierungsdetails verursacht (wie die Fälle Insert-Update und Insert-Delete).
Der Update-Update-Fall funktioniert nicht, da er intern wie das Halloween-Problem aussieht und Postgres nicht wissen kann, welche Tupel zweimal aktualisiert werden könnten und welche das Halloween-Problem wieder einführen könnten.
Der Grund ist also der gleiche (wie modifizierende CTEs implementiert werden und wie jeder CTE denselben Snapshot sieht), aber die Details unterscheiden sich in diesen beiden Fällen, da sie komplexer sind und die Ergebnisse im Fall von Update-Update unvorhersehbar sein können.
Im Insert-Update (wie in Ihrem Fall) und einem ähnlichen Insert-Delete sind die Ergebnisse vorhersehbar. Nur das Einfügen erfolgt, da der zweite Vorgang (Aktualisieren oder Löschen) die neu eingefügten Zeilen nicht sehen und beeinflussen kann.
Die vorgeschlagene Lösung ist jedoch für alle Fälle gleich, in denen versucht wird, dieselben Zeilen mehrmals zu ändern: Tun Sie es nicht. Schreiben Sie entweder Anweisungen, die jede Zeile einmal ändern, oder verwenden Sie separate (2 oder mehr) Anweisungen.