PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Restituisce i valori delle colonne precedenti all'AGGIORNAMENTO utilizzando solo SQL

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