Kilka miesięcy temu nauczyłem się z odpowiedzi na temat przepełnienia stosu, jak wykonywać wiele aktualizacji jednocześnie w MySQL przy użyciu następującej składni:
INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);
Teraz przełączyłem się na PostgreSQL i najwyraźniej nie jest to poprawne. Odnosi się do wszystkich poprawnych tabel, więc zakładam, że chodzi o użycie różnych słów kluczowych, ale nie jestem pewien, gdzie w dokumentacji PostgreSQL jest to uwzględnione.
Aby to wyjaśnić, chcę wstawić kilka rzeczy i jeśli już istnieją, aby je zaktualizować.
sql
postgresql
upsert
sql-merge
Teifion
źródło
źródło
Odpowiedzi:
PostgreSQL od wersji 9.5 ma składnię UPSERT z klauzulą ON CONFLICT . o następującej składni (podobnej do MySQL)
Wyszukiwanie w archiwach grup e-mailowych postgresql hasła „upsert” prowadzi do znalezienia przykładu robienia tego, co prawdopodobnie chcesz zrobić, w instrukcji :
Być może istnieje przykład, jak to zrobić zbiorczo, używając CTE w wersji 9.1 i wyższej, na liście mailingowej hakerów :
Zobacz odpowiedź a_horse_with_no_name jest dla wyraźniejszego przykład.
źródło
excluded
dotyczy pierwsze rozwiązanie tutaj?excluded
tabela zapewnia dostęp do wartości, które próbujesz WSTAWIĆ.Ostrzeżenie: nie jest to bezpieczne, jeśli jest wykonywane z wielu sesji jednocześnie (patrz zastrzeżenia poniżej).
Innym sprytnym sposobem wykonania „UPSERT” w postgresql jest zrobienie dwóch sekwencyjnych instrukcji UPDATE / INSERT, z których każda ma na celu odniesienie sukcesu lub brak efektu.
AKTUALIZACJA powiedzie się, jeśli wiersz o identyfikatorze „id = 3” już istnieje, w przeciwnym razie nie będzie miał wpływu.
INSERT powiedzie się tylko wtedy, gdy wiersz o identyfikatorze „id = 3” jeszcze nie istnieje.
Możesz połączyć te dwa w jeden ciąg i uruchomić je oba za pomocą pojedynczej instrukcji SQL wykonanej z aplikacji. Zdecydowanie zalecane jest uruchomienie ich razem w jednej transakcji.
Działa to bardzo dobrze, gdy jest uruchamiany w izolacji lub na zablokowanej tabeli, ale podlega warunkom wyścigu, co oznacza, że może nadal zawieść z duplikatem błędu klucza, jeśli wiersz jest wstawiany jednocześnie lub może zakończyć się bez wstawienia wiersza, gdy wiersz jest usuwany jednocześnie .
SERIALIZABLE
Transakcja na PostgreSQL 9.1 lub wyższy będzie go obsługiwać niezawodnie kosztem bardzo wysoki wskaźnik awaryjności serializacji, czyli trzeba będzie ponowić dużo. Zobacz, dlaczego upsert jest tak skomplikowany , co omawia ten przypadek bardziej szczegółowo.Podejście to podlega
read committed
insert
update
także utraconym aktualizacjom w oderwaniu, chyba że aplikacja sprawdzi, czy wiersz, którego dotyczy luka, i zweryfikuje, czy wiersz lub wiersz, którego dotyczy problem .źródło
... where not exists (select 1 from table where id = 3);
read committed
izolacji chyba twoi aplikacja sprawdza, aby upewnić się, żeinsert
alboupdate
mają niezerową rowcount. Zobacz dba.stackexchange.com/q/78510/7788W PostgreSQL 9.1 można to osiągnąć za pomocą zapisywalnego CTE ( wspólne wyrażenie tabelowe ):
Zobacz te wpisy na blogu:
Pamiętaj, że to rozwiązanie nie zapobiega unikatowemu naruszeniu klucza, ale nie jest podatne na utracone aktualizacje.
Zobacz kontynuację Craig Ringer na dba.stackexchange.com
źródło
UPDATE
wpływ na dowolne wiersze.W PostgreSQL 9.5 i nowszych możesz używać
INSERT ... ON CONFLICT UPDATE
.Zobacz dokumentację .
MySQL
INSERT ... ON DUPLICATE KEY UPDATE
można bezpośrednio przekształcić w aON CONFLICT UPDATE
. Nie ma też standardowej składni SQL, oba są rozszerzeniami specyficznymi dla bazy danych. SąMERGE
ku temu dobre powody , nowa składnia nie została stworzona tylko dla zabawy. (Składnia MySQL ma również problemy, które oznaczają, że nie została przyjęta bezpośrednio).np. podana konfiguracja:
zapytanie MySQL:
staje się:
Różnice:
Państwo musi podać nazwę kolumny (lub unikalną nazwę wiązania) do wykorzystania przy sprawdzaniu niepowtarzalności. To jest
ON CONFLICT (columnname) DO
SET
Należy użyć słowa kluczowego , jakby to było normalneUPDATE
zdanieMa też kilka fajnych funkcji:
Możesz mieć na sobie
WHERE
klauzulęUPDATE
(pozwalającą ci efektywnie zamieniaćON CONFLICT UPDATE
sięON CONFLICT IGNORE
w określone wartości)Wartości proponowane do wstawienia są dostępne jako zmienna wierszowa
EXCLUDED
, która ma taką samą strukturę jak tabela docelowa. Możesz uzyskać oryginalne wartości w tabeli, używając nazwy tabeli. Tak więc w tym przypadkuEXCLUDED.c
będzie10
(ponieważ to właśnie próbowaliśmy wstawić) i"table".c
będzie tak,3
ponieważ jest to bieżąca wartość w tabeli. Możesz użyć jednego lub obu wSET
wyrażeniach iWHERE
klauzulach.Aby uzyskać informacje na temat upsert, zobacz Jak UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) w PostgreSQL?
źródło
ON DUPLICATE KEY UPDATE
. Pobrałem Postgres 9.5 i zaimplementowałem Twój kod, ale dziwnie ten sam problem występuje w Postgres: pole szeregowe klucza podstawowego nie jest kolejne (między wstawkami a aktualizacjami są luki). Masz pomysł, co się tutaj dzieje? Czy to normalne? Masz pomysł, jak uniknąć tego zachowania? Dziękuję Ci.SERIAL
/SEQUENCE
lubAUTO_INCREMENT
nie posiadające luki. Jeśli potrzebujesz sekwencji bez przerw, są one bardziej złożone; zazwyczaj musisz użyć tabeli liczników. Google powie Ci więcej. Należy jednak pamiętać, że sekwencje bez przerw zapobiegają wszelkiej współbieżności wstawiania.BEGIN ... EXCEPTION ...
przebiega w podtransakcji, która jest przywracana po błędzie, przyrost sekwencji zostanie cofnięty, jeśli sięINSERT
nie powiedzie.Szukałem tego samego, kiedy tu przyjechałem, ale brak ogólnej funkcji „upsert” trochę mnie niepokoi, więc pomyślałem, że możesz po prostu przekazać aktualizację i wstawić sql jako argumenty tej funkcji z instrukcji
wyglądałoby to tak:
i być może, aby zrobić to, co początkowo chciałeś zrobić, wsadowo „upsert”, możesz użyć Tcl, aby podzielić sql_update i zapętlić poszczególne aktualizacje, trafienie wydajności będzie bardzo małe, patrz http://archives.postgresql.org/pgsql- performance / 2006-04 / msg00557.php
najwyższy koszt to wykonanie zapytania z twojego kodu, po stronie bazy danych koszt wykonania jest znacznie niższy
źródło
DELETE
chyba że zablokujesz tabelę lub nie będziesz wSERIALIZABLE
izolacji transakcji na PostgreSQL 9.1 lub nowszym.Nie ma na to prostego polecenia.
Najbardziej poprawnym podejściem jest użycie funkcji, takiej jak ta z dokumentów .
Innym rozwiązaniem (choć nie tak bezpiecznym) jest wykonanie aktualizacji ze zwróceniem, sprawdzenie, które wiersze były aktualizacjami, i wstawienie pozostałych
Coś w stylu:
przy założeniu, że zwrócono 2:
Oczywiście prędzej czy później wyskoczy (w równoczesnym środowisku), ponieważ jest tu wyraźny warunek wyścigu, ale zwykle będzie działał.
Oto dłuższy i bardziej wyczerpujący artykuł na ten temat .
źródło
Osobiście skonfigurowałem „regułę” dołączoną do instrukcji insert. Załóżmy, że masz tabelę „dns”, która rejestruje liczbę trafień dns na klienta według czasu:
Chcesz móc ponownie wstawiać wiersze ze zaktualizowanymi wartościami lub tworzyć je, jeśli jeszcze nie istniały. Wprowadzono identyfikator klienta i godzinę. Coś takiego:
Aktualizacja: może się to nie powieść, jeśli wystąpią jednoczesne wstawienia, ponieważ wygeneruje wyjątki wyjątków_nieprzestrzeganie. Jednak transakcja, która nie zostanie zakończona, będzie kontynuowana i zakończy się sukcesem, wystarczy powtórzyć zakończoną transakcję.
Jednakże, jeśli przez cały czas dzieje się mnóstwo wstawek, będziesz chciał umieścić blokadę tabeli wokół instrukcji wstawiania: blokowanie SHARE ROW EXCLUSIVE zapobiegnie wszelkim operacjom, które mogłyby wstawiać, usuwać lub aktualizować wiersze w tabeli docelowej. Jednak aktualizacje, które nie aktualizują unikatowego klucza, są bezpieczne, więc jeśli żadna operacja tego nie zrobi, użyj zamiast tego blokad doradczych.
Ponadto polecenie KOPIUJ nie używa ZASAD, więc jeśli wstawiasz za pomocą KOPIOWANIA, musisz zamiast tego użyć wyzwalaczy.
źródło
Używam tej funkcji scalania
źródło
update
pierwszego, a następnie sprawdzenie liczby zaktualizowanych wierszy. (Zobacz odpowiedź Ahmada)Niestandardową funkcję „wstawiania” powyżej, jeśli chcesz WSTAWIĆ I WYMIENIĆ:
`
A po wykonaniu wykonaj coś takiego:
Ważne jest, aby wstawić podwójny przecinek w celu uniknięcia błędów kompilatora
źródło
Podobne do najbardziej lubianej odpowiedzi, ale działa nieco szybciej:
(źródło: http://www.the-art-of-web.com/sql/upsert/ )
źródło
Mam ten sam problem z zarządzaniem ustawieniami konta, co pary wartości nazwa. Kryteria projektowe są takie, że różni klienci mogą mieć różne zestawy ustawień.
Moje rozwiązanie, podobnie jak JWP, polega na masowym usuwaniu i zastępowaniu, generując rekord scalania w Twojej aplikacji.
Jest to dość kuloodporne, niezależne od platformy, a ponieważ nigdy nie ma więcej niż około 20 ustawień na klienta, są to tylko 3 dość mało wywołania db db - prawdopodobnie najszybsza metoda.
Alternatywą aktualizacji poszczególnych wierszy - sprawdzania wyjątków, a następnie wstawiania - lub jakiejś kombinacji jest ohydny kod, powolny i często psuje się, ponieważ (jak wspomniano powyżej) niestandardowa obsługa wyjątków SQL zmienia się z db na db - lub nawet z wydania na wydanie.
źródło
REPLACE INTO
niżINSERT INTO ... ON DUPLICATE KEY UPDATE
, co może powodować problem, jeśli użyjesz wyzwalaczy. Skończysz usuwanie i wstawianie wyzwalaczy / reguł zamiast aktualizować je.Zgodnie z dokumentacją PostgreSQL
INSERT
instrukcji obsługaON DUPLICATE KEY
sprawy nie jest obsługiwana. Ta część składni jest zastrzeżonym rozszerzeniem MySQL.źródło
MERGE
jest również bardziej operacją OLAP; wyjaśnienia znajdziesz na stackoverflow.com/q/17267417/398670 . Nie definiuje semantyki współbieżności i większość ludzi, którzy używają jej do wstawiania, po prostu tworzy błędy.źródło
Do łączenia małych zestawów dobrze jest użyć powyższej funkcji. Jeśli jednak scalasz duże ilości danych, sugeruję zajrzenie na http://mbk.projects.postgresql.org
Obecna najlepsza praktyka, o której wiem, to:
źródło
UPDATE zwróci liczbę zmodyfikowanych wierszy. Jeśli używasz JDBC (Java), możesz następnie sprawdzić tę wartość na 0 i, jeśli nie wpłynie to na żadne wiersze, uruchom zamiast tego INSERT. Jeśli używasz innego języka programowania, być może nadal można uzyskać liczbę zmodyfikowanych wierszy, sprawdź dokumentację.
To może nie być tak eleganckie, ale masz znacznie prostszy SQL, który jest bardziej trywialny w użyciu z kodu wywołującego. Inaczej, jeśli napiszesz skrypt dziesięcioliniowy w PL / PSQL, prawdopodobnie powinieneś mieć test jednostkowy tego lub innego rodzaju tylko dla niego samego.
źródło
Edycja: To nie działa zgodnie z oczekiwaniami. W przeciwieństwie do przyjętej odpowiedzi, powoduje to unikalne naruszenia klucza, gdy dwa procesy wielokrotnie się wywołują
upsert_foo
jednocześnie.Eureka! Wymyśliłem sposób na zrobienie tego w jednym zapytaniu: użyj,
UPDATE ... RETURNING
aby sprawdzić, czy dotyczy to któregokolwiek wiersza:Należy
UPDATE
to zrobić w oddzielnej procedurze, ponieważ niestety jest to błąd składniowy:Teraz działa zgodnie z oczekiwaniami:
źródło