sql >> Databasteknik >  >> RDS >> PostgreSQL

Returnera kolumnvärden före UPPDATERING endast med SQL

Problem

Manualen förklarar:

Den valfria RETURNING klausul orsakar UPDATE 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 i FROM , kan beräknas. De nya (efter uppdatering) värdena i tabellens kolumner används . Syntaxen för RETURNING listan är identisk med den för utdatalistan för SELECT .

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



  1. Skapa en "Senast ändrad" kolumn i SQL Server

  2. PostgreSQL, komplex fråga för att beräkna ingredienser efter recept

  3. PostgreSQL procedurspråk C hittades inte

  4. Några idéer om resurspooling på låg nivå i PostgreSQL