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

Jak zaktualizować wszystkie kolumny za pomocą INSERT ... ON CONFLICT ...?

UPDATE składnia wymaga jawnie nazwać kolumny docelowe. Możliwe powody, aby tego uniknąć:

  • Masz wiele kolumn i chcesz tylko skrócić składnię.
  • Ty nie wiesz nazwy kolumn z wyjątkiem unikalnych kolumn.

"All columns" musi oznaczać "wszystkie kolumny tabeli docelowej" (lub przynajmniej "wiodące kolumny tabeli" ) w kolejności zgodnej i zgodnym typie danych. W przeciwnym razie i tak musiałbyś podać listę nazw kolumn docelowych.

Stół testowy:

CREATE TABLE tbl (
   id    int PRIMARY KEY
 , text  text
 , extra text
);

INSERT INTO tbl AS t
VALUES (1, 'foo')
     , (2, 'bar');

1. DELETE &INSERT zamiast tego w pojedynczym zapytaniu

Bez znajomości nazw kolumn oprócz id .

Działa tylko dla „wszystkich kolumn tabeli docelowej” . Chociaż składnia działa nawet dla wiodącego podzbioru, nadmiarowe kolumny w tabeli docelowej zostaną zresetowane do wartości NULL za pomocą DELETE i INSERT .

UPSERT (INSERT ... ON CONFLICT ... ) jest potrzebne, aby uniknąć problemów ze współbieżnością / blokowaniem przy jednoczesnym obciążeniu zapisu, i tylko dlatego, że nie ma ogólnego sposobu blokowania nieistniejących wierszy w Postgresie (blokowanie wartości ).

Twoje specjalne wymagania dotyczą tylko UPDATE część. Możliwe komplikacje nie mają zastosowania tam, gdzie istnieje dotyczy to wierszy. Te są prawidłowo zamknięte. Upraszczając trochę więcej, możesz zredukować sprawę do DELETE i INSERT :

WITH data(id) AS (              -- Only 1st column gets explicit name!
   VALUES
      (1, 'foo_upd', 'a')       -- changed
    , (2, 'bar', 'b')           -- unchanged
    , (3, 'baz', 'c')           -- new
   )
, del AS (
   DELETE FROM tbl AS t
   USING  data d
   WHERE  t.id = d.id
   -- AND    t <> d              -- optional, to avoid empty updates
   )                             -- only works for complete rows
INSERT INTO tbl AS t
TABLE  data                      -- short for: SELECT * FROM data
ON     CONFLICT (id) DO NOTHING
RETURNING t.id;

W modelu Postgres MVCC UPDATE jest w dużej mierze tym samym co DELETE i INSERT w każdym razie (z wyjątkiem niektórych przypadków narożnych ze współbieżnością, aktualizacjami HOT i dużymi wartościami kolumn przechowywanymi poza linią). Ponieważ i tak chcesz zastąpić wszystkie wiersze, po prostu usuń sprzeczne wiersze przed INSERT . Usunięte wiersze pozostają zablokowane, dopóki transakcja nie zostanie zatwierdzona. INSERT może znaleźć sprzeczne wiersze tylko dla wcześniej nieistniejących wartości kluczy, jeśli jednoczesna transakcja wstawi je jednocześnie (po DELETE , ale przed INSERT ).

W tym szczególnym przypadku utracisz dodatkowe wartości kolumn dla wierszy, których dotyczy problem. Nie zgłoszono wyjątku. Ale jeśli konkurencyjne zapytania mają ten sam priorytet, nie stanowi to problemu:drugie zapytanie wygrało dla niektórych wydziwianie. Ponadto, jeśli inne zapytanie jest podobnym UPSERT, jego alternatywą jest oczekiwanie na zatwierdzenie tej transakcji, a następnie natychmiastowe aktualizowanie. „Zwycięstwo” może być zwycięstwem pyrrusowym.

Informacje o „pustych aktualizacjach”:

  • Jak (lub mogę) WYBRAĆ ODRÓŻNIENIE w wielu kolumnach?

Nie, moje zapytanie musi wygrać!

OK, prosiłeś o to:

WITH data(id) AS (                   -- Only 1st column gets explicit name!
   VALUES                            -- rest gets default names "column2", etc.
     (1, 'foo_upd', NULL)              -- changed
   , (2, 'bar', NULL)                  -- unchanged
   , (3, 'baz', NULL)                  -- new
   , (4, 'baz', NULL)                  -- new
   )
, ups AS (
   INSERT INTO tbl AS t
   TABLE  data                       -- short for: SELECT * FROM data
   ON     CONFLICT (id) DO UPDATE
   SET    id = t.id
   WHERE  false                      -- never executed, but locks the row!
   RETURNING t.id
   )
, del AS (
   DELETE FROM tbl AS t
   USING  data     d
   LEFT   JOIN ups u USING (id)
   WHERE  u.id IS NULL               -- not inserted !
   AND    t.id = d.id
   -- AND    t <> d                  -- avoid empty updates - only for full rows
   RETURNING t.id
   )
, ins AS (
   INSERT INTO tbl AS t
   SELECT *
   FROM   data
   JOIN   del USING (id)             -- conflict impossible!
   RETURNING id
   )
SELECT ARRAY(TABLE ups) AS inserted  -- with UPSERT
     , ARRAY(TABLE ins) AS updated   -- with DELETE & INSERT;

Jak?

  • Pierwszy CTE data tylko dostarcza dane. Może zamiast tego być stołem.
  • Drugie CTE ups :UPERT. Wiersze ze sprzecznym id nie są zmieniane, ale także zablokowane .
  • Trzeci CTE del usuwa sprzeczne wiersze. Pozostają zamknięte.
  • Czwarte ins CTE wstawia całe wiersze . Dozwolone tylko dla tej samej transakcji
  • Ostateczny WYBÓR służy tylko do demonstracji, aby pokazać, co się stało.

Aby sprawdzić pusty test aktualizacji (przed i po) za pomocą:

SELECT ctid, * FROM tbl; -- did the ctid change?

(zakomentowany) sprawdza wszelkie zmiany w wierszu AND t <> d działa nawet z wartościami NULL, ponieważ porównujemy dwie wpisane wartości wierszy zgodnie z instrukcją:

dwie wartości NULL są uważane za równe, a NULL jest uważane za większe niż inne niż NULL

2. Dynamiczny SQL

Działa to również dla podzbioru wiodących kolumn, zachowując istniejące wartości.

Sztuką jest umożliwienie Postgresowi dynamicznego zbudowania ciągu zapytania z nazwami kolumn z katalogów systemowych, a następnie wykonanie go.

Zobacz powiązane odpowiedzi dotyczące kodu:

  • Zaktualizuj wiele kolumn w funkcji wyzwalacza w plpgsql

  • Zbiorcza aktualizacja wszystkich kolumn

  • SQL aktualizuje pola jednej tabeli z pól innej



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Django:Jakie są najlepsze praktyki migracji projektu z sqlite do PostgreSQL?

  2. Co ::robi w PostgreSQL?

  3. Jak Asind() działa w PostgreSQL

  4. Kolumny agregujące z dodatkowymi (odrębnymi) filtrami

  5. BŁĄD:funkcje w wyrażeniu indeksu muszą być oznaczone jako IMMUTABLE w Postgresie