Problème
Le manuel explique :
Le
RETURNING
facultatif clause provoqueUPDATE
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 dansFROM
, peut être calculé. Les nouvelles valeurs (post-mise à jour) des colonnes de la table sont utilisées . La syntaxe duRETURNING
la liste est identique à celle de la liste de sortie deSELECT
.
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