Problem
Manualen förklarar:
Den valfria
RETURNING
klausul orsakarUPDATE
att beräkna och returvärden baserat på varje rad som faktiskt uppdateras. Alla uttryck som använder tabellens kolumner och/eller kolumner i andra tabeller som nämns iFROM
, kan beräknas. De nya (efter uppdatering) värdena i tabellens kolumner används . Syntaxen förRETURNING
listan är identisk med den för utdatalistan förSELECT
.
Djärv betoning min. Det finns inget sätt att komma åt den gamla raden i en RETURNING
klausul. Du kan kringgå denna begränsning med en trigger eller en separat SELECT
före UPDATE
insvept i en transaktion eller insvept i en CTE som kommenterades.
Det du försöker uppnå fungerar dock perfekt om du går med i en annan instans av tabellen i FROM
klausul:
Lösning utan samtidiga skrivningar
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;
Returnerar:
old_id | old_name | tbl_id | name
--------+----------+--------+---------
3 | Old Guy | 23 | New Guy
Kolumnen/kolumnerna som används för att själv gå med måste vara UNIQUE NOT NULL
. I det enkla exemplet, WHERE
villkoret finns i samma kolumn tbl_id
, men det är bara en slump. Fungerar för alla villkor.
Jag testade detta med PostgreSQL-versioner från 8.4 till 13.
Det är annorlunda för INSERT
:
- INSERT I ... FRÅN SELECT ... RETURNERAR ID-mappningar
Lösningar med samtidig skrivbelastning
Det finns olika sätt att undvika tävlingsförhållanden med samtidiga skrivoperationer på samma rader. (Observera att samtidiga skrivoperationer på orelaterade rader inte är några problem alls.) Den enkla, långsamma och säkra (men dyra) metoden är att köra transaktionen med SERIALIZABLE
isoleringsnivå:
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE ... ;
COMMIT;
Men det är nog överdrivet. Och du måste vara beredd att upprepa operationen i händelse av ett serialiseringsfel.
Enklare och snabbare (och lika tillförlitlig med samtidig skrivbelastning) är ett explicit lås på en rad som ska uppdateras:
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;
Notera hur WHERE
villkoret flyttat till underfrågan (kan återigen vara vad som helst ), och endast självanslutningen (på UNIQUE NOT NULL
kolumn(er)) finns kvar i den yttre frågan. Detta garanterar att endast rader är låsta av den inre SELECT
bearbetas. WHERE
villkoren kan lösas till en annan uppsättning rader en stund senare.
Se:
- Atomic UPDATE .. VÄLJ i Postgres
db<>spela här
Gammal sqlfiddle