Problema
Il manuale spiega:
Il RETURNING
facoltativo la clausola provoca UPDATE
per calcolare e restituire valori in base a ciascuna riga effettivamente aggiornata. Qualsiasi espressione che utilizzi le colonne della tabella e/o le colonne di altre tabelle menzionate in FROM
, può essere calcolato. Vengono utilizzati i nuovi valori (post-aggiornamento) delle colonne della tabella . La sintassi del RETURNING
list è identico a quello dell'elenco di output di SELECT
.
Enfasi in grassetto mio. Non c'è modo di accedere alla vecchia riga in un RETURNING
clausola. Puoi aggirare questa restrizione con un trigger o un SELECT
separato prima il UPDATE
racchiuso in una transazione o racchiuso in un CTE come è stato commentato.
Tuttavia, ciò che stai cercando di ottenere funziona perfettamente se ti unisci a un'altra istanza della tabella nel FROM
clausola:
Soluzione senza scritture simultanee
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;
Resi:
old_id | old_name | tbl_id | name
--------+----------+--------+---------
3 | Old Guy | 23 | New Guy
Le colonne utilizzate per l'auto-unione devono essere UNIQUE NOT NULL
. Nel semplice esempio, il WHERE
la condizione è sulla stessa colonna tbl_id
, ma è solo una coincidenza. Funziona per qualsiasi condizioni.
L'ho testato con le versioni di PostgreSQL dalla 8.4 alla 13.
È diverso per INSERT
:
- INSERT IN ... FROM SELECT ... RETURNING mappature id
Soluzioni con carico di scrittura simultaneo
Esistono vari modi per evitare race condition con operazioni di scrittura simultanee sulle stesse righe. (Si noti che le operazioni di scrittura simultanee su righe non correlate non sono affatto un problema.) Il metodo semplice, lento e sicuro (ma costoso) consiste nell'eseguire la transazione con SERIALIZABLE
livello di isolamento:
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE ... ;
COMMIT;
Ma probabilmente è eccessivo. E devi essere pronto a ripetere l'operazione in caso di errore di serializzazione.
Più semplice e veloce (e altrettanto affidabile con il carico di scrittura simultaneo) è un blocco esplicito su uno riga da aggiornare:
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;
Nota come il WHERE
condizione spostata nella sottoquery (di nuovo, può essere qualsiasi cosa ), e solo il self-join (su UNIQUE NOT NULL
column(s)) rimane nella query esterna. Ciò garantisce che solo le righe siano bloccate dal SELECT
interno vengono elaborati. Il WHERE
le condizioni potrebbero risolversi in un diverso insieme di righe un momento dopo.
Vedi:
- Aggiornamento atomico .. SELEZIONA in Postgres
db<>gioca qui
Sqlfiddle vecchio