Mam dwie kolumny w tabeli col1
, col2
obie są niepowtarzalnie indeksowane (col1 jest unikalny, podobnie jak col2).
Muszę wstawić do tej tabeli, użyć ON CONFLICT
składni i zaktualizować inne kolumny, ale nie mogę użyć obu kolumn w conflict_target
klauzuli.
To działa:
INSERT INTO table
...
ON CONFLICT ( col1 )
DO UPDATE
SET
-- update needed columns here
Ale jak to zrobić dla kilku kolumn, coś takiego:
...
ON CONFLICT ( col1, col2 )
DO UPDATE
SET
....
postgresql
upsert
postgresql-9.5
Oto Shavadze
źródło
źródło
Odpowiedzi:
Przykładowa tabela i dane
CREATE TABLE dupes(col1 int primary key, col2 int, col3 text, CONSTRAINT col2_unique UNIQUE (col2) ); INSERT INTO dupes values(1,1,'a'),(2,2,'b');
Odtwarzanie problemu
INSERT INTO dupes values(3,2,'c') ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2
Nazwijmy to Q1. Wynik to
Co mówi dokumentacja
Daje to wrażenie, że poniższe zapytanie powinno zadziałać, ale tak nie jest, ponieważ w rzeczywistości wymagałoby to wspólnego, unikalnego indeksu dla kol1 i kol2. Jednak taki indeks nie gwarantowałby, że col1 i col2 będą niepowtarzalne indywidualnie, co jest jednym z wymagań PO.
INSERT INTO dupes values(3,2,'c') ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2
Nazwijmy to zapytanie Q2 (kończy się to błędem składniowym)
Czemu?
Postgresql zachowuje się w ten sposób, ponieważ to, co powinno się stać, gdy wystąpi konflikt w drugiej kolumnie, nie jest dobrze zdefiniowane. Możliwości jest wiele. Na przykład w powyższym zapytaniu Q1, czy należy aktualizować postgresql,
col1
gdy występuje konfliktcol2
? Ale co, jeśli to prowadzi do kolejnego konfliktucol1
? jak postgresql ma sobie z tym poradzić?Rozwiązanie
Rozwiązaniem jest połączenie ON CONFLICT ze staromodnym UPSERTem .
CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS $$ BEGIN LOOP -- first try to update the key UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2; IF found THEN RETURN; END IF; -- not there, so try to insert the key -- if someone else inserts the same key concurrently, or key2 -- already exists in col2, -- we could get a unique-key failure BEGIN INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data; RETURN; EXCEPTION WHEN unique_violation THEN BEGIN INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data; RETURN; EXCEPTION WHEN unique_violation THEN -- Do nothing, and loop to try the UPDATE again. END; END; END LOOP; END; $$ LANGUAGE plpgsql;
Musisz zmodyfikować logikę tej zapisanej funkcji, aby aktualizowała kolumny dokładnie tak, jak chcesz. Wywołaj to jak
SELECT merge_db(3,2,'c'); SELECT merge_db(1,2,'d');
źródło
ON CONFLICT
wymaga unikalnego indeksu * do wykrywania konfliktów. Musisz więc tylko utworzyć unikalny indeks dla obu kolumn:t=# create table t (id integer, a text, b text); CREATE TABLE t=# create unique index idx_t_id_a on t (id, a); CREATE INDEX t=# insert into t values (1, 'a', 'foo'); INSERT 0 1 t=# insert into t values (1, 'a', 'bar') on conflict (id, a) do update set b = 'bar'; INSERT 0 1 t=# select * from t; id | a | b ----+---+----- 1 | a | bar
* Oprócz unikalnych indeksów można również użyć ograniczeń wykluczających . Są to nieco bardziej ogólne niż unikalne ograniczenia. Załóżmy, że w Twojej tabeli są kolumny
id
ivalid_time
(ivalid_time
jest to atsrange
) i chcesz zezwolić na zduplikowane wartościid
, ale nie na nakładające się okresy. Unikalne ograniczenie ci nie pomoże, ale z ograniczeniem wykluczającym możesz powiedzieć „wyklucz nowe rekordy, jeśli sąid
równe stare,id
a takżevalid_time
pokrywają się z nimivalid_time
”.źródło
ON CONFLICT
?on conflict
polecenia. Błąd to po prostu „kolumna my_index_name nie istnieje”.W dzisiejszych czasach jest (wydaje się) niemożliwe. Ani ostatnia wersja
ON CONFLICT
składni nie pozwala na powtórzenie klauzuli, ani z CTE nie jest możliwe: nie jest możliwe złamanie INSERT z ON CONFLICT, aby dodać więcej celów konfliktu.źródło
Jeśli używasz postgres 9.5, możesz użyć WYŁĄCZONEJ przestrzeni.
Przykład zaczerpnięty z Co nowego w PostgreSQL 9.5 :
INSERT INTO user_logins (username, logins) VALUES ('Naomi',1),('James',1) ON CONFLICT (username) DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;
źródło
ALBO I
źródło
Vlad wpadł na dobry pomysł.
Najpierw musisz utworzyć unikalne ograniczenie tabeli na kolumnach.Po
col1, col2
wykonaniu tej czynności możesz wykonać następujące czynności:INSERT INTO dupes values(3,2,'c') ON CONFLICT ON CONSTRAINT dupes_pkey DO UPDATE SET col3 = 'c', col2 = 2
źródło
Trochę hacky, ale rozwiązałem to, łącząc dwie wartości z col1 i col2 w nową kolumnę, col3 (coś w rodzaju indeksu dwóch) i porównując z tym. Działa to tylko wtedy, gdy potrzebujesz, aby pasowało do obu kol1 i kol2.
INSERT INTO table ... ON CONFLICT ( col3 ) DO UPDATE SET -- update needed columns here
Gdzie col3 = konkatenacja wartości z col1 i col2.
źródło
on conflict
.Zwykle (tak mi się wydaje) można wygenerować oświadczenie tylko z jednym
on conflict
określającym jedyne ograniczenie, które ma znaczenie dla wstawianej rzeczy.Ponieważ zazwyczaj tylko jedno ograniczenie jest „istotne” w danym momencie. (Jeśli jest ich wiele, to zastanawiam się, czy coś jest dziwne / dziwnie zaprojektowane, hmm.)
Przykład:
(Licencja: nie CC0, tylko CC-By)
// there're these unique constraints: // unique (site_id, people_id, page_id) // unique (site_id, people_id, pages_in_whole_site) // unique (site_id, people_id, pages_in_category_id) // and only *one* of page-id, category-id, whole-site-true/false // can be specified. So only one constraint is "active", at a time. val thingColumnName = thingColumnName(notfificationPreference) val insertStatement = s""" insert into page_notf_prefs ( site_id, people_id, notf_level, page_id, pages_in_whole_site, pages_in_category_id) values (?, ?, ?, ?, ?, ?) -- There can be only one on-conflict clause. on conflict (site_id, people_id, $thingColumnName) <—— look do update set notf_level = excluded.notf_level """ val values = List( siteId.asAnyRef, notfPref.peopleId.asAnyRef, notfPref.notfLevel.toInt.asAnyRef, // Only one of these is non-null: notfPref.pageId.orNullVarchar, if (notfPref.wholeSite) true.asAnyRef else NullBoolean, notfPref.pagesInCategoryId.orNullInt) runUpdateSingleRow(insertStatement, values)
I:
on conflict
Klauzula jest generowana dynamicznie, w zależności od tego, co próbuję zrobić. Jeśli wstawiam preferencje dotyczące powiadomień dla strony - może wystąpić wyjątkowy konflikt dotyczącysite_id, people_id, page_id
ograniczenia. A jeśli konfiguruję ustawienia powiadomień dla kategorii - zamiast tego wiem, że ograniczenie, które może zostać naruszone, tosite_id, people_id, category_id
.Więc mogę, i całkiem prawdopodobne, że Ty też, w twoim przypadku ?, wygenerować poprawną
on conflict (... columns )
, ponieważ wiem, co chcę zrobić, a potem wiem, które z wielu unikalnych ograniczeń jest tym, które może zostać naruszone.źródło
ON CONFLICT to bardzo niezdarne rozwiązanie, uciekaj
UPDATE dupes SET key1=$1, key2=$2 where key3=$3 if rowcount > 0 INSERT dupes (key1, key2, key3) values ($1,$2,$3);
działa na Oracle, Postgres i wszystkich innych bazach danych
źródło