sql >> Database >  >> RDS >> PostgreSQL

Pre-UPDATE kolomwaarden retourneren met alleen SQL

Probleem

De handleiding legt uit:

De optionele RETURNING clausule veroorzaakt UPDATE om te berekenen en waarde(n) terug te geven op basis van elke rij die daadwerkelijk is bijgewerkt. Elke expressie die de kolommen van de tabel en/of kolommen van andere tabellen gebruikt die worden genoemd in FROM , kan worden berekend. De nieuwe (post-update) waarden van de kolommen van de tabel worden gebruikt . De syntaxis van de RETURNING lijst is identiek aan die van de uitvoerlijst van SELECT .

Vetgedrukte nadruk van mij. Er is geen manier om toegang te krijgen tot de oude rij in een RETURNING clausule. Je kunt deze beperking omzeilen met een trigger of een aparte SELECT voor de UPDATE verpakt in een transactie of verpakt in een CTE zoals werd opgemerkt.

Wat u echter probeert te bereiken werkt prima als u lid wordt van een andere instantie van de tabel in de FROM clausule:

Oplossing zonder gelijktijdige schrijfbewerkingen

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;

Retourneren:

 old_id | old_name | tbl_id |  name
--------+----------+--------+---------
  3     | Old Guy  | 23     | New Guy

De kolom(men) die worden gebruikt om zelf lid te worden, moeten UNIQUE NOT NULL zijn . In het eenvoudige voorbeeld, de WHERE voorwaarde staat in dezelfde kolom tbl_id , maar dat is gewoon toeval. Werkt voor elke voorwaarden.

Ik heb dit getest met PostgreSQL-versies van 8.4 tot 13.

Het is anders voor INSERT :

  • INSERT INTO ... FROM SELECT ... RETURNING id mappings

Oplossingen met gelijktijdige schrijfbelasting

Er zijn verschillende manieren om race-omstandigheden te vermijden met gelijktijdige schrijfbewerkingen op dezelfde rijen. (Merk op dat gelijktijdige schrijfbewerkingen op niet-gerelateerde rijen helemaal geen probleem zijn.) De eenvoudige, langzame en zekere (maar dure) methode is om de transactie uit te voeren met SERIALIZABLE isolatieniveau:

BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE ... ;
COMMIT;

Maar dat is waarschijnlijk overdreven. En u moet voorbereid zijn om de handeling te herhalen in het geval van een serialisatiefout.

Eenvoudiger en sneller (en net zo betrouwbaar met gelijktijdige schrijfbelasting) is een expliciete vergrendeling op de één rij die moet worden bijgewerkt:

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;

Merk op hoe de WHERE voorwaarde verplaatst naar de subquery (nogmaals, kan alles zijn ), en alleen de self-join (op UNIQUE NOT NULL kolom(men)) blijft in de buitenste query. Dit garandeert dat alleen rijen worden vergrendeld door de binnenste SELECT zijn verwerkt. De WHERE omstandigheden kunnen even later worden omgezet in een andere reeks rijen.

Zie:

  • Atomic UPDATE .. SELECT in Postgres

db<>viool hier
Oude sqlfiddle



  1. Groeperen op twee velden gebruiken en tellen in SQL

  2. Hoe fouten in sqlplus weer te geven

  3. DB-migratie met de NextForm Multi-Table Wizard

  4. Hoe zich te ontdoen van MySQL-fout 'Voorbereide instructie moet opnieuw worden voorbereid'