Problem
Instrukcja wyjaśnia:
Opcjonalny RETURNING klauzula powoduje UPDATE do obliczenia i zwrócenia wartości na podstawie każdego faktycznie zaktualizowanego wiersza. Dowolne wyrażenie używające kolumn tabeli i/lub kolumn innych tabel wymienionych w FROM , można obliczyć. Używane są nowe (po aktualizacji) wartości kolumn tabeli . Składnia RETURNING lista jest identyczna z listą wyjściową SELECT .
Moje odważne podkreślenie. Nie ma możliwości uzyskania dostępu do starego wiersza w RETURNING klauzula. Możesz obejść to ograniczenie za pomocą wyzwalacza lub oddzielnego SELECT przed UPDATE opakowane w transakcję lub opakowane w CTE, jak skomentowano.
Jednak to, co próbujesz osiągnąć, działa doskonale jeśli dołączysz do innej instancji tabeli w FROM klauzula:
Rozwiązanie bez równoczesnych zapisów
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;
Zwroty:
old_id | old_name | tbl_id | name
--------+----------+--------+---------
3 | Old Guy | 23 | New Guy
Kolumny używane do samodzielnego łączenia muszą być UNIQUE NOT NULL . W prostym przykładzie WHERE warunek znajduje się w tej samej kolumnie tbl_id , ale to tylko zbieg okoliczności. Działa dla każdego warunki.
Testowałem to z wersjami PostgreSQL od 8.4 do 13.
Inaczej jest w przypadku INSERT :
- WSTAW DO ... Z WYBIERZ ... POWRACAJĄC mapowania identyfikatorów
Rozwiązania z jednoczesnym ładowaniem zapisu
Istnieją różne sposoby uniknięcia sytuacji wyścigu przy współbieżnych operacjach zapisu w tych samych wierszach. (Zauważ, że współbieżne operacje zapisu na niepowiązanych wierszach nie stanowią żadnego problemu.) Prostą, powolną i pewną (ale kosztowną) metodą jest uruchomienie transakcji z SERIALIZABLE poziom izolacji:
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE ... ;
COMMIT;
Ale to prawdopodobnie przesada. Musisz być przygotowany na powtórzenie operacji w przypadku niepowodzenia serializacji.
Prostsze i szybsze (i tak samo niezawodne przy jednoczesnym ładowaniu zapisu) jest jawna blokada na jednym wiersz do aktualizacji:
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;
Zwróć uwagę, jak WHERE warunek przeniesiony do podzapytania (ponownie, może być wszystkim ) i tylko samodołączenie (na UNIQUE NOT NULL kolumna(y)) pozostaje w zewnętrznym zapytaniu. Gwarantuje to, że tylko wiersze zablokowane przez wewnętrzny SELECT są przetwarzane. WHERE warunki mogą chwilę później rozwiązać inny zestaw wierszy.
Zobacz:
- ATOMIC UPDATE .. SELECT w Postgresie
db<>graj tutaj
Stary sqlfiddle