jak emulować „wstaw ignorowanie” i „przy zduplikowanej aktualizacji klucza” (scalanie sql) za pomocą postgresql?

140

Niektóre serwery SQL mają funkcję, która INSERTjest pomijana, jeśli naruszyłoby to ograniczenie klucza podstawowego / unikalnego. Na przykład MySQL ma INSERT IGNORE.

Jaki jest najlepszy sposób na emulację INSERT IGNOREi ON DUPLICATE KEY UPDATEużywanie PostgreSQL?

gpilotino
źródło
Zobacz też: stackoverflow.com/questions/5269590/…
Dave Jarvis
6
od 9.5, jest to możliwe natywnie: stackoverflow.com/a/34639631/4418
warren
Emulacja MySQL: ON DUPLICATE KEY UPDATEna PgSQL 9.5 jest nadal trochę niemożliwa, ponieważ ON CLAUSEodpowiednik PgSQL wymaga podania nazwy ograniczenia, podczas gdy MySQL może przechwycić każde ograniczenie bez potrzeby jego definiowania. Uniemożliwia mi to „emulowanie” tej funkcji bez przepisywania zapytań.
NeverEndingQueue,

Odpowiedzi:

35

Spróbuj wykonać AKTUALIZACJĘ. Jeśli nie modyfikuje żadnego wiersza, co oznacza, że ​​nie istniał, więc zrób wstawianie. Oczywiście robisz to w ramach transakcji.

Możesz oczywiście zawinąć to w funkcję, jeśli nie chcesz umieszczać dodatkowego kodu po stronie klienta. Potrzebujesz także pętli dla bardzo rzadkich warunków wyścigu w tym myśleniu.

Przykład tego można znaleźć w dokumentacji: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html , przykład 40-2 na dole.

To zwykle najłatwiejszy sposób. Możesz zrobić trochę magii z zasadami, ale prawdopodobnie będzie to dużo bardziej bałagan. Poleciłbym podejście polegające na zawijaniu funkcji każdego dnia.

Działa to dla wartości w jednym wierszu lub w kilku wierszach. Jeśli masz do czynienia z dużą liczbą wierszy, na przykład z podzapytania, najlepiej jest podzielić je na dwa zapytania, jedno dla WSTAWIANIA i jedno dla AKTUALIZACJI (oczywiście jako odpowiednie sprzężenie / podwybór - nie ma potrzeby wpisywania głównego filtruj dwukrotnie)

Magnus Hagander
źródło
4
„Jeśli masz do czynienia z dużą liczbą wierszy”, to dokładnie mój przypadek. Chcę zbiorczo aktualizować / wstawiać wiersze, a dzięki mysql mogę to zrobić za pomocą tylko JEDNEGO zapytania bez zapętlenia. Teraz zastanawiam się, czy jest to możliwe również z postgresql: aby użyć tylko jednego zapytania do zbiorczej aktualizacji LUB wstawienia. Mówisz: „najlepiej jest podzielić to na dwa zapytania, jedno dla INSERT i jedno dla UPDATE”, ale jak mogę zrobić wstawianie, które nie powoduje błędów w zduplikowanych kluczach? (tj. „INSERT IGNORE”)
gpilotino
4
Magnus miał na myśli, że używasz zapytania takiego: "rozpocznij transakcję; utwórz tymczasową tabelę tymczasową tabelę jako wybierz * z testu, gdzie fałsz; skopiuj tymczasową tabelę z 'data_file.csv'; zablokuj test tabeli; zaktualizuj zestaw testowy data = tymczasowa_tabela.data z tymczasowej_tabeli gdzie test.id = tymczasowa_tabela.id; wstaw do testu wybierz * z tabeli tymczasowej, gdzie nie ma identyfikatora (wybierz identyfikator z testu) jako "
Tometzky
25
Aktualizacja: dzięki PostgreSQL 9.5 jest to teraz tak proste, jak INSERT ... ON CONFLICT DO NOTHING;. Zobacz też odpowiedź stackoverflow.com/a/34639631/2091700 .
Alphaaa
Ważne, SQL standard MERGEjest nie sejfu upsert współbieżności, chyba że wziąć LOCK TABLEpierwszy. Ludzie używają tego w ten sposób, ale to jest złe.
Craig Ringer
1
W wersji 9.5 jest to teraz funkcja „natywna”, więc sprawdź komentarz @Alphaaa (tylko reklamujący komentarz reklamujący odpowiedź)
Camilo Delvasto
178

W PostgreSQL 9.5 jest to teraz natywna funkcjonalność (podobnie jak MySQL od kilku lat):

WSTAW ... W KONFLIKCIE NIC NIE ROBIĆ / AKTUALIZUJ („UPSERT”)

9.5 zapewnia obsługę operacji „UPSERT”. INSERT jest rozszerzony, aby akceptować klauzulę ON CONFLICT DO UPDATE / IGNORE. Ta klauzula określa alternatywne działanie, jakie należy podjąć w przypadku podwójnego naruszenia.

...

Dalszy przykład nowej składni:

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1) 
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;
królikarnia
źródło
100

Edycja: jeśli przegapiłeś odpowiedź Warrena, PG9.5 ma to teraz natywnie; czas na upgrade!


Opierając się na odpowiedzi Billa Karwina, aby wyjaśnić, jak wyglądałoby podejście oparte na regułach (przenoszenie z innego schematu w tej samej bazie danych i przy użyciu wielokolumnowego klucza podstawowego):

CREATE RULE "my_table_on_duplicate_ignore" AS ON INSERT TO "my_table"
  WHERE EXISTS(SELECT 1 FROM my_table 
                WHERE (pk_col_1, pk_col_2)=(NEW.pk_col_1, NEW.pk_col_2))
  DO INSTEAD NOTHING;
INSERT INTO my_table SELECT * FROM another_schema.my_table WHERE some_cond;
DROP RULE "my_table_on_duplicate_ignore" ON "my_table";

Uwaga: reguła ma zastosowanie do wszystkich INSERToperacji, dopóki reguła nie zostanie usunięta, więc nie całkiem ad hoc.

EoghanM
źródło
@sema masz na myśli, jeśli another_schema.my_tablezawiera duplikaty zgodnie z ograniczeniami my_table?
EoghanM,
2
@EoghanM Przetestowałem regułę w postgresql 9.3 i nadal mogłem wstawiać duplikaty z wieloma wierszami instrukcji wstawiania, jak np. INSERT INTO "my_table" (a, b), (a, b); (Zakładając, że wiersz (a, b) jeszcze nie istniał w „my_table”.)
sema,
@sema, gotcha - to musi oznaczać, że reguła jest wykonywana na początku dla wszystkich danych do wstawienia i nie jest wykonywana ponownie po wstawieniu każdego wiersza. Jednym podejściem byłoby wstawienie danych najpierw do innej tabeli tymczasowej, która nie ma żadnych ograniczeń, a następnie zrobienieINSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;
EoghanM
@EoghanM Innym podejściem jest tymczasowe złagodzenie ograniczeń duplikatów i zaakceptowanie duplikatów przy wstawianiu, ale później usunięcie duplikatów za pomocąDELETE FROM my_table WHERE ctid IN (SELECT ctid FROM (SELECT ctid,ROW_NUMBER() OVER (PARTITION BY pk_col_1,pk_col_2) AS rn FROM my_table) AS dups WHERE dups.rn > 1);
sema
Mam problem opisany przez @sema. Jeśli zrobię wstawkę (a, b), (a, b), wyrzuca błąd. Czy jest sposób, aby stłumić błędy, również w tym przypadku?
Diogo Melo
35

Dla tych z Was, którzy mają Postgres 9.5 lub nowszy, nowa składnia ON CONFLICT DO NIC powinna działać:

INSERT INTO target_table (field_one, field_two, field_three ) 
SELECT field_one, field_two, field_three
FROM source_table
ON CONFLICT (field_one) DO NOTHING;

Dla tych z nas, którzy mają wcześniejszą wersję, to prawo łączenia będzie działać zamiast:

INSERT INTO target_table (field_one, field_two, field_three )
SELECT source_table.field_one, source_table.field_two, source_table.field_three
FROM source_table 
LEFT JOIN target_table ON source_table.field_one = target_table.field_one
WHERE target_table.field_one IS NULL;
hanmari
źródło
Drugie podejście nie działa przy tworzeniu dużej wstawki w środowisku współbieżnym. Otrzymasz, Unique violation: 7 ERROR: duplicate key value violates unique constraintkiedy target_tablewstawiono inny wiersz do niego podczas wykonywania tego zapytania, jeśli ich klucze rzeczywiście się duplikują. Wierzę, że blokowanie target_tablepomoże, ale współbieżność oczywiście ucierpi.
G. Kashtanov
1
ON CONFLICT (field_one) DO NOTHINGto najlepsza część odpowiedzi.
Abel Callejo
24

Aby uzyskać logikę wstawiania ignorowania , możesz zrobić coś jak poniżej. Zauważyłem, że po prostu wstawianie wartości dosłownych z instrukcji select działa najlepiej, a następnie możesz zamaskować zduplikowane klucze za pomocą klauzuli NOT EXISTS. Podejrzewam, że aby uzyskać aktualizację zduplikowanej logiki, potrzebna byłaby pętla pl / pgsql.

INSERT INTO manager.vin_manufacturer
(SELECT * FROM( VALUES
  ('935',' Citroën Brazil','Citroën'),
  ('ABC', 'Toyota', 'Toyota'),
  ('ZOM',' OM','OM')
  ) as tmp (vin_manufacturer_id, manufacturer_desc, make_desc)
  WHERE NOT EXISTS (
    --ignore anything that has already been inserted
    SELECT 1 FROM manager.vin_manufacturer m where m.vin_manufacturer_id = tmp.vin_manufacturer_id)
)
Keyo
źródło
Co się stanie, jeśli tmp zawiera zduplikowany wiersz, co może się zdarzyć?
Henley Chiu,
Zawsze możesz wybrać za pomocą odrębnego słowa kluczowego.
Keyo,
5
Podobnie jak FYI, sztuczka „GDZIE NIE ISTNIEJE” nie działa w przypadku wielu transakcji, ponieważ różne transakcje nie widzą nowo dodanych danych z innych transakcji.
Dave Johansen
21
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')
user2342158
źródło
Jaki jest wpływ wielu transakcji, które próbują zrobić to samo? Czy to możliwe, że pomiędzy wykonaniem gdzie nie istnieje a insert wykonującym jakąś inną transakcję wstawia wiersz? A jeśli Postgres może temu zapobiec, to czy postgres nie wprowadza punktu synchronizacji we wszystkich tych transakcjach, kiedy to osiągną?
Καrτhικ
Nie działa to w przypadku wielu transakcji, ponieważ nowo dodane dane nie są widoczne dla innych transakcji.
Dave Johansen
12

Wygląda na to, że PostgreSQL obsługuje obiekt schematu o nazwie a regułą .

http://www.postgresql.org/docs/current/static/rules-update.html

Możesz utworzyć regułę ON INSERTdla danej tabeli, robiąc to, NOTHINGjeśli istnieje wiersz z daną wartością klucza podstawowego, lub wykonując UPDATEzamiast tego, INSERTjeśli istnieje wiersz z daną wartością klucza podstawowego.

Sam tego nie próbowałem, więc nie mogę mówić z doświadczenia ani dawać przykładu.

Bill Karwin
źródło
1
jeśli dobrze zrozumiałem, te reguły są wyzwalaczami, które są wykonywane za każdym razem, gdy wywoływana jest instrukcja. co jeśli chcę zastosować regułę tylko do jednego zapytania? muszę stworzyć regułę, a potem natychmiast ją zniszczyć? (a co z warunkami wyścigu?)
gpilotino
3
Tak, też miałbym te same pytania. Mechanizm reguł jest najbliższą rzeczą, jaką mogłem znaleźć w PostgreSQL, do MySQL INSERT IGNORE lub ON DUPLICATE KEY UPDATE. Jeśli wyszukamy w Google hasło „postgresql przy zduplikowanej aktualizacji klucza”, znajdziesz innych ludzi, którzy zalecają mechanizm reguły, nawet jeśli reguła miałaby zastosowanie do dowolnego INSERT, a nie tylko na zasadzie ad hoc.
Bill Karwin
4
PostgreSQL obsługuje transakcyjny DDL, co oznacza, że ​​jeśli utworzysz regułę i upuścisz ją w ramach pojedynczej transakcji, reguła nigdy nie będzie widoczna poza tą transakcją (i dlatego nigdy nie będzie miała żadnego efektu poza) tą transakcją.
cdhowie
6

Jak @hanmari wspomniał w swoim komentarzu. przy wstawianiu do tabel postgres, funkcja on konfliktu (..) nic nie robi, jest najlepszym kodem, aby nie wstawiać zduplikowanych danych .:

query = "INSERT INTO db_table_name(column_name)
         VALUES(%s) ON CONFLICT (column_name) DO NOTHING;"

Linia kodu ON CONFLICT pozwoli instrukcji insert na dalsze wstawianie wierszy danych. Kod zapytania i wartości to przykład wstawienia daty z Excela do tabeli dbresy postgres. Mam ograniczenia dodane do tabeli postgres, której używam, aby upewnić się, że pole ID jest unikalne. Zamiast wykonywać usuwanie wierszy danych, które są takie same, dodaję wiersz kodu sql, który przenumeruje kolumnę ID, zaczynając od 1. Przykład:

q = 'ALTER id_column serial RESTART WITH 1'

Jeśli moje dane mają pole ID, nie używam go jako podstawowego identyfikatora / identyfikatora seryjnego, tworzę kolumnę ID i ustawiam ją na serial. Mam nadzieję, że te informacje są pomocne dla wszystkich. * Nie mam wyższego wykształcenia w zakresie programowania / kodowania. Wszystko, co wiem z kodowania, uczę się sam.

Yankeeownz
źródło
to nie działa na złożonych unikalnych indeksach!
Nulik
4

To rozwiązanie pozwala uniknąć używania reguł:

BEGIN
   INSERT INTO tableA (unique_column,c2,c3) VALUES (1,2,3);
EXCEPTION 
   WHEN unique_violation THEN
     UPDATE tableA SET c2 = 2, c3 = 3 WHERE unique_column = 1;
END;

ale ma wadę wydajności (patrz PostgreSQL.org ):

Wejście i wyjście bloku zawierającego klauzulę EXCEPTION jest znacznie droższe niż blok bez niej. Dlatego nie używaj EXCEPTION bez potrzeby.

Numer cztery
źródło
1

Zbiorczo zawsze możesz usunąć wiersz przed wstawką. Usunięcie wiersza, który nie istnieje, nie powoduje błędu, więc jest bezpiecznie pomijany.

David Noriega
źródło
2
Takie podejście będzie dość podatne na dziwne warunki wyścigowe, nie polecałbym go ...
Steven Schlansker
1
+1 To jest łatwe i ogólne. Jeśli jest używany ostrożnie, może to być w rzeczywistości proste rozwiązanie.
Wouter van Nifterick
1
Nie zadziała również, gdy istniejące dane zostały zmienione po wstawieniu (ale nie na zduplikowanym kluczu) i chcemy zachować aktualizacje. Jest to scenariusz, w którym istnieją skrypty SQL napisane dla kilku nieco różnych systemów, takich jak aktualizacje bazy danych, które działają w systemach produkcyjnych, QA, deweloperskich i testowych.
Hanno Fietz
1
Klucz obcy może nie stanowić problemu, jeśli utworzysz go z DEFERRABLE INITIALLY DEFERREDflagami.
temoto
-1

W przypadku skryptów importu danych, aby w pewnym sensie zastąpić „JEŚLI NIE ISTNIEJE”, istnieje nieco niezręczne sformułowanie, które jednak działa:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
analytik_work
źródło