Problem
Das Handbuch erklärt:
Das optionale RETURNING
-Klausel bewirkt UPDATE
um Werte basierend auf jeder tatsächlich aktualisierten Zeile zu berechnen und zurückzugeben. Jeder Ausdruck, der die Spalten der Tabelle und/oder Spalten anderer Tabellen verwendet, die in FROM
erwähnt werden , kann berechnet werden. Die neuen (nach der Aktualisierung) Werte der Tabellenspalten werden verwendet . Die Syntax von RETURNING
Die Liste ist identisch mit der Ausgabeliste von SELECT
.
Fette Hervorhebung von mir. Es gibt keine Möglichkeit, in einem RETURNING
auf die alte Zeile zuzugreifen Klausel. Sie können diese Einschränkung mit einem Trigger oder einem separaten SELECT
umgehen vorher das UPDATE
verpackt in einer Transaktion oder verpackt in einem CTE, wie kommentiert wurde.
Das, was Sie erreichen möchten, funktioniert jedoch einwandfrei wenn Sie mit einer anderen Instanz der Tabelle in FROM
verknüpfen Klausel:
Lösung ohne gleichzeitige Schreibvorgänge
UPDATE tbl x
SET tbl_id = 23
, name = 'New Guy'
FROM tbl y -- using the FROM clause
WHERE x.tbl_id = y.tbl_id -- must be UNIQUE NOT NULL
AND x.tbl_id = 3
RETURNING y.tbl_id AS old_id, y.name AS old_name
, x.tbl_id , x.name;
Rückgabe:
old_id | old_name | tbl_id | name
--------+----------+--------+---------
3 | Old Guy | 23 | New Guy
Die für die Selbstverknüpfung verwendeten Spalten müssen UNIQUE NOT NULL
sein . Im einfachen Beispiel das WHERE
Bedingung befindet sich in derselben Spalte tbl_id
, aber das ist nur Zufall. Funktioniert für alle Bedingungen.
Ich habe dies mit PostgreSQL-Versionen von 8.4 bis 13 getestet.
Anders bei INSERT
:
- INSERT INTO ... FROM SELECT ... RETURNING ID-Zuordnungen
Lösungen mit gleichzeitiger Schreiblast
Es gibt verschiedene Möglichkeiten, Race-Conditions mit gleichzeitigen Schreibvorgängen in denselben Zeilen zu vermeiden. (Beachten Sie, dass gleichzeitige Schreiboperationen auf nicht zusammenhängende Zeilen überhaupt kein Problem darstellen.) Die einfache, langsame und sichere (aber teure) Methode besteht darin, die Transaktion mit SERIALIZABLE
auszuführen Isolationsstufe:
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE ... ;
COMMIT;
Aber das ist wohl übertrieben. Und Sie müssen darauf vorbereitet sein, den Vorgang im Falle eines Serialisierungsfehlers zu wiederholen.
Einfacher und schneller (und ebenso zuverlässig bei gleichzeitiger Schreiblast) ist eine explizite Sperre für one zu aktualisierende Zeile:
UPDATE tbl x
SET tbl_id = 24
, name = 'New Gal'
FROM (SELECT tbl_id, name FROM tbl WHERE tbl_id = 4 FOR UPDATE) y
WHERE x.tbl_id = y.tbl_id
RETURNING y.tbl_id AS old_id, y.name AS old_name
, x.tbl_id , x.name;
Beachten Sie, wie der WHERE
Bedingung in die Unterabfrage verschoben (kann wieder alles sein ) und nur der Self-Join (auf UNIQUE NOT NULL
Spalte(n)) bleibt in der äußeren Abfrage. Dies garantiert, dass nur Zeilen durch das innere SELECT
gesperrt werden verarbeitet werden. Das WHERE
Bedingungen können einen Moment später in einen anderen Satz von Zeilen aufgelöst werden.
Siehe:
- Atomic UPDATE .. SELECT in Postgres
db<>hier fummeln
Altes sqlfiddle