PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Zwróć wartości kolumn sprzed UPDATE, używając tylko SQL

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



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak pracować z ułamkami dziesiętnymi o wysokiej precyzji w PHP?

  2. kursor.execute(WSTAW WARTOŚCI im_entry.test (+entrym+) ('+p+');)

  3. Wyrażenie regularne w klauzuli PostgreSQL LIKE

  4. Formatuj miesiąc cyframi rzymskimi w PostgreSQL

  5. CTE i paradoks urodzin