PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Renvoyer les valeurs de colonne pré-UPDATE en utilisant SQL uniquement

Problème

Le manuel explique :

Le RETURNING facultatif clause provoque UPDATE pour calculer et renvoyer des valeurs en fonction de chaque ligne réellement mise à jour. Toute expression utilisant les colonnes de la table et/ou les colonnes d'autres tables mentionnées dans FROM , peut être calculé. Les nouvelles valeurs (post-mise à jour) des colonnes de la table sont utilisées . La syntaxe du RETURNING la liste est identique à celle de la liste de sortie de SELECT .

Bold emphase mienne. Il n'y a aucun moyen d'accéder à l'ancienne ligne dans un RETURNING clause. Vous pouvez contourner cette restriction avec un déclencheur ou un SELECT séparé avant la UPDATE enveloppé dans une transaction ou enveloppé dans un CTE comme cela a été commenté.

Cependant, ce que vous essayez d'obtenir fonctionne parfaitement bien si vous vous joignez à une autre instance de la table dans le FROM clause :

Solution sans écritures simultanées

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;

Renvoie :

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

La ou les colonnes utilisées pour l'auto-jointure doivent être UNIQUE NOT NULL . Dans l'exemple simple, le WHERE la condition est sur la même colonne tbl_id , mais ce n'est qu'une coïncidence. Fonctionne pour tous conditions.

J'ai testé cela avec les versions PostgreSQL de 8.4 à 13.

C'est différent pour INSERT :

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

Solutions avec charge d'écriture simultanée

Il existe plusieurs façons d'éviter les conditions de concurrence avec des opérations d'écriture simultanées sur les mêmes lignes. (Notez que les opérations d'écriture simultanées sur des lignes non liées ne posent aucun problème.) La méthode simple, lente et sûre (mais coûteuse) consiste à exécuter la transaction avec SERIALIZABLE niveau d'isolement :

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

Mais c'est probablement exagéré. Et vous devez être prêt à répéter l'opération en cas d'échec de la sérialisation.

Plus simple et plus rapide (et tout aussi fiable avec une charge d'écriture simultanée) est un verrou explicite sur le one ligne à mettre à jour :

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;

Notez comment le WHERE condition déplacée vers la sous-requête (encore une fois, peut être n'importe quoi ), et uniquement l'auto-jointure (sur UNIQUE NOT NULL colonne(s)) reste dans la requête externe. Cela garantit que seules les lignes verrouillées par le SELECT interne sont traités. Le WHERE les conditions peuvent se résoudre en un ensemble de lignes différent un instant plus tard.

Voir :

  • MISE À JOUR atomique .. SELECT dans Postgres

db<>jouez ici
Vieux sqlfiddle