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 sprzecznymid
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