Vor einigen Monaten habe ich aus einer Antwort auf Stack Overflow gelernt, wie mehrere Updates gleichzeitig in MySQL mit der folgenden Syntax ausgeführt werden:
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);
Ich habe jetzt auf PostgreSQL umgestellt und anscheinend ist dies nicht korrekt. Es bezieht sich auf alle korrekten Tabellen, daher gehe ich davon aus, dass verschiedene Schlüsselwörter verwendet werden, aber ich bin mir nicht sicher, wo dies in der PostgreSQL-Dokumentation behandelt wird.
Zur Verdeutlichung möchte ich einige Dinge einfügen und wenn sie bereits vorhanden sind, um sie zu aktualisieren.
sql
postgresql
upsert
sql-merge
Teifion
quelle
quelle
Antworten:
PostgreSQL hat seit Version 9.5 die UPSERT- Syntax mit der ON CONFLICT- Klausel. mit der folgenden Syntax (ähnlich wie MySQL)
Wenn Sie in den E-Mail-Gruppenarchiven von postgresql nach "upsert" suchen, finden Sie im Handbuch ein Beispiel dafür, was Sie möglicherweise tun möchten :
Möglicherweise gibt es in der Hacker-Mailingliste ein Beispiel dafür, wie dies in großen Mengen mithilfe von CTEs in Version 9.1 und höher durchgeführt werden kann :
Siehe a_horse_with_no_name Antwort für ein klareres Beispiel.
quelle
excluded
sich das in der ersten Lösung hier?excluded
Tabelle Zugriff auf die Werte, die Sie ursprünglich einfügen wollten.Warnung: Dies ist nicht sicher, wenn mehrere Sitzungen gleichzeitig ausgeführt werden (siehe Vorsichtsmaßnahmen unten).
Eine andere clevere Möglichkeit, ein "UPSERT" in postgresql zu erstellen, besteht darin, zwei aufeinanderfolgende UPDATE / INSERT-Anweisungen auszuführen, die jeweils so konzipiert sind, dass sie erfolgreich sind oder keine Auswirkungen haben.
Das UPDATE ist erfolgreich, wenn bereits eine Zeile mit "id = 3" vorhanden ist, andernfalls hat es keine Auswirkung.
Das INSERT ist nur erfolgreich, wenn die Zeile mit "id = 3" noch nicht vorhanden ist.
Sie können diese beiden zu einer einzigen Zeichenfolge kombinieren und beide mit einer einzigen SQL-Anweisung ausführen, die von Ihrer Anwendung ausgeführt wird. Es wird dringend empfohlen, sie zusammen in einer einzigen Transaktion auszuführen.
Dies funktioniert sehr gut, wenn es isoliert oder in einer gesperrten Tabelle ausgeführt wird, unterliegt jedoch Rennbedingungen, die bedeuten, dass es bei gleichzeitiger Einfügung einer Zeile immer noch mit einem doppelten Schlüsselfehler fehlschlägt oder beim gleichzeitigen Löschen einer Zeile ohne eingefügte Zeile endet . Eine
SERIALIZABLE
Transaktion unter PostgreSQL 9.1 oder höher wird auf Kosten einer sehr hohen Serialisierungsfehlerrate zuverlässig abgewickelt, was bedeutet, dass Sie viel wiederholen müssen. Sehen Sie, warum Upsert so kompliziert ist , und diskutieren Sie diesen Fall ausführlicher.Dieser Ansatz unterliegt auch isoliert verlorenen Aktualisierungen, es
read committed
sei denn, die Anwendung überprüft die Anzahl der betroffenen Zeilen und überprüft, ob entweder dieinsert
oder dieupdate
betroffene Zeile vorhanden ist .quelle
... where not exists (select 1 from table where id = 3);
read committed
Isolation , es sei denn Ihre Anwendung überprüft , um sicherzustellen , dass derinsert
oder dieupdate
eine von Null verschiedenen rowcount hat. Siehe dba.stackexchange.com/q/78510/7788Mit PostgreSQL 9.1 kann dies mit einem beschreibbaren CTE ( Common Table Expression ) erreicht werden:
Siehe diese Blogeinträge:
Beachten Sie, dass diese Lösung eine eindeutige Schlüsselverletzung nicht verhindert, jedoch nicht für verlorene Updates anfällig ist.
Siehe das Follow-up von Craig Ringer auf dba.stackexchange.com
quelle
UPDATE
Zeilen betroffen sind.In PostgreSQL 9.5 und höher können Sie verwenden
INSERT ... ON CONFLICT UPDATE
.Siehe die Dokumentation .
Ein MySQL
INSERT ... ON DUPLICATE KEY UPDATE
kann direkt in a umformuliert werdenON CONFLICT UPDATE
. SQL-Standard-Syntax ist ebenfalls nicht vorhanden, beide sind datenbankspezifische Erweiterungen. Es gibt gute Gründe, warum diesMERGE
nicht verwendet wurde. Eine neue Syntax wurde nicht nur zum Spaß erstellt. (Die Syntax von MySQL weist auch Probleme auf, die bedeuten, dass sie nicht direkt übernommen wurde.)zB gegebenes Setup:
die MySQL-Abfrage:
wird:
Unterschiede:
Sie müssen den Spaltennamen (oder den eindeutigen Einschränkungsnamen) angeben, der für die Eindeutigkeitsprüfung verwendet werden soll. Das ist das
ON CONFLICT (columnname) DO
Das Schlüsselwort
SET
muss so verwendet werden, als wäre dies eine normaleUPDATE
AnweisungEs hat auch einige nette Funktionen:
Sie können eine
WHERE
Klausel auf Ihrem habenUPDATE
(damit Sie sich effektivON CONFLICT UPDATE
inON CONFLICT IGNORE
bestimmte Werte verwandeln können)Die zum Einfügen vorgeschlagenen Werte sind als Zeilenvariable verfügbar
EXCLUDED
, die dieselbe Struktur wie die Zieltabelle hat. Sie können die ursprünglichen Werte in der Tabelle mithilfe des Tabellennamens abrufen. Also in diesem FallEXCLUDED.c
sein wird10
(denn das ist , was wir versuchen , einfügen) und"table".c
wird ,3
da , dass der aktuelle Wert in der Tabelle ist. Sie können einen oder beide in denSET
Ausdrücken und derWHERE
Klausel verwenden.Hintergrundinformationen zu Upsert finden Sie unter UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) in PostgreSQL.
quelle
ON DUPLICATE KEY UPDATE
. Ich habe Postgres 9.5 heruntergeladen und Ihren Code implementiert, aber seltsamerweise tritt unter Postgres das gleiche Problem auf: Das serielle Feld des Primärschlüssels ist nicht fortlaufend (es gibt Lücken zwischen den Einfügungen und Aktualisierungen). Irgendeine Idee, was hier los ist? Ist das normal? Irgendeine Idee, wie man dieses Verhalten vermeidet? Vielen Dank.SERIAL
/SEQUENCE
oderAUTO_INCREMENT
keine Lücken aufweisen. Wenn Sie lückenlose Sequenzen benötigen, sind diese komplexer. Normalerweise müssen Sie einen Zählertisch verwenden. Google wird Ihnen mehr erzählen. Beachten Sie jedoch, dass lückenlose Sequenzen alle Einfügungen gleichzeitig verhindern.BEGIN ... EXCEPTION ...
Ausführung in einer Subtransaktion erfolgt, die bei einem Fehler zurückgesetzt wird, wird Ihr Sequenzinkrement zurückgesetzt, wenn diesINSERT
fehlschlägt.Ich habe nach dem gleichen gesucht, als ich hierher kam, aber das Fehlen einer generischen "Upsert" -Funktion hat mich ein wenig gestört, so dass ich dachte, Sie könnten einfach das Update übergeben und SQL als Argumente für diese Funktion aus dem Handbuch einfügen
das würde so aussehen:
und vielleicht, um das zu tun, was Sie ursprünglich tun wollten, Batch "Upsert", könnten Sie Tcl verwenden, um das sql_update zu teilen und die einzelnen Updates zu schleifen. Der Leistungstreffer wird sehr gering sein, siehe http://archives.postgresql.org/pgsql- Leistung / 2006-04 / msg00557.php
Die höchsten Kosten sind die Ausführung der Abfrage aus Ihrem Code. Auf der Datenbankseite sind die Ausführungskosten viel geringer
quelle
DELETE
sei denn, Sie sperren die Tabelle oder befinden sich inSERIALIZABLE
PostgreSQL 9.1 oder höher in Transaktionsisolation.Es gibt keinen einfachen Befehl, dies zu tun.
Der korrekteste Ansatz besteht darin, Funktionen wie die aus Dokumenten zu verwenden .
Eine andere Lösung (obwohl nicht so sicher) besteht darin, ein Update mit Rückgabe durchzuführen, zu überprüfen, welche Zeilen aktualisiert wurden, und den Rest einzufügen
Etwas in der Art von:
Angenommen, ID: 2 wurde zurückgegeben:
Natürlich wird es früher oder später (in einer gleichzeitigen Umgebung) aussteigen, da hier klare Rennbedingungen herrschen, aber normalerweise wird es funktionieren.
Hier ist ein längerer und umfassenderer Artikel zum Thema .
quelle
Persönlich habe ich eine "Regel" eingerichtet, die der Einfügeanweisung beigefügt ist. Angenommen, Sie hatten eine "DNS" -Tabelle, in der DNS-Treffer pro Kunde pro Zeit aufgezeichnet wurden:
Sie wollten in der Lage sein, Zeilen mit aktualisierten Werten erneut einzufügen oder sie zu erstellen, wenn sie noch nicht vorhanden waren. Geben Sie die customer_id und die Uhrzeit ein. Etwas wie das:
Update: Dies kann fehlschlagen, wenn gleichzeitig Einfügungen durchgeführt werden, da dies zu eindeutigen Ausnahmen für Gewalt führt. Die nicht abgebrochene Transaktion wird jedoch fortgesetzt und ist erfolgreich, und Sie müssen nur die abgebrochene Transaktion wiederholen.
Wenn jedoch ständig Unmengen von Einfügungen auftreten, sollten Sie die Einfügeanweisungen mit einer Tabellensperre versehen: Durch die Sperrung von SHARE ROW EXCLUSIVE werden Vorgänge verhindert, durch die Zeilen in Ihre Zieltabelle eingefügt, gelöscht oder aktualisiert werden können. Aktualisierungen, bei denen der eindeutige Schlüssel nicht aktualisiert wird, sind jedoch sicher. Wenn Sie dies nicht tun, verwenden Sie stattdessen Hinweisschlösser.
Außerdem verwendet der Befehl COPY keine REGELN. Wenn Sie also mit COPY einfügen, müssen Sie stattdessen Trigger verwenden.
quelle
Ich benutze diese Funktion zusammenführen
quelle
update
erste Zeile auszuführen und dann die Anzahl der aktualisierten Zeilen zu überprüfen. (Siehe Ahmads Antwort)Ich habe oben die Funktion "Upsert" angepasst, wenn Sie EINFÜGEN UND ERSETZEN möchten:
`
Und nachdem Sie ausgeführt haben, machen Sie so etwas:
Es ist wichtig, ein doppeltes Dollar-Komma zu setzen, um Compilerfehler zu vermeiden
quelle
Ähnlich wie die beliebteste Antwort, funktioniert jedoch etwas schneller:
(Quelle: http://www.the-art-of-web.com/sql/upsert/ )
quelle
Ich habe das gleiche Problem beim Verwalten von Kontoeinstellungen wie Name-Wert-Paare. Das Designkriterium ist, dass verschiedene Clients unterschiedliche Einstellungssätze haben können.
Meine Lösung, ähnlich wie bei JWP, besteht darin, sie in großen Mengen zu löschen und zu ersetzen und den Zusammenführungsdatensatz in Ihrer Anwendung zu generieren.
Dies ist ziemlich kugelsicher, plattformunabhängig und da es nie mehr als 20 Einstellungen pro Client gibt, sind dies nur 3 Datenbankaufrufe mit relativ geringer Last - wahrscheinlich die schnellste Methode.
Die Alternative, einzelne Zeilen zu aktualisieren - nach Ausnahmen zu suchen und dann einzufügen - oder eine Kombination davon ist abscheulicher Code, der langsam ist und häufig unterbrochen wird, weil (wie oben erwähnt) die nicht standardmäßige Behandlung von SQL-Ausnahmen von Datenbank zu Datenbank wechselt - oder sogar von Version zu Version.
quelle
REPLACE INTO
alsINSERT INTO ... ON DUPLICATE KEY UPDATE
, was ein Problem verursachen kann, wenn Sie Trigger verwenden. Am Ende werden Sie Trigger / Regeln löschen und einfügen, anstatt diese zu aktualisieren.Gemäß der PostgreSQL-Dokumentation der
INSERT
Anweisung wird die Behandlung desON DUPLICATE KEY
Falls nicht unterstützt. Dieser Teil der Syntax ist eine proprietäre MySQL-Erweiterung.quelle
MERGE
ist auch eher eine OLAP-Operation; Erläuterungen finden Sie unter stackoverflow.com/q/17267417/398670 . Es definiert keine Parallelitätssemantik und die meisten Leute, die es für Upsert verwenden, erstellen nur Fehler.quelle
Zum Zusammenführen kleiner Mengen ist die Verwendung der obigen Funktion in Ordnung. Wenn Sie jedoch große Datenmengen zusammenführen, empfehlen wir Ihnen, http://mbk.projects.postgresql.org zu besuchen
Die derzeitige Best Practice, die mir bekannt ist, ist:
quelle
UPDATE gibt die Anzahl der geänderten Zeilen zurück. Wenn Sie JDBC (Java) verwenden, können Sie diesen Wert mit 0 vergleichen und stattdessen INSERT auslösen, wenn keine Zeilen betroffen sind. Wenn Sie eine andere Programmiersprache verwenden, kann die Anzahl der geänderten Zeilen möglicherweise noch abgerufen werden. Überprüfen Sie die Dokumentation.
Dies ist möglicherweise nicht so elegant, aber Sie haben viel einfacheres SQL, das aus dem aufrufenden Code trivialer zu verwenden ist. Wenn Sie das zehnzeilige Skript in PL / PSQL schreiben, sollten Sie wahrscheinlich einen Unit-Test der einen oder anderen Art nur dafür durchführen.
quelle
Bearbeiten: Dies funktioniert nicht wie erwartet. Im Gegensatz zur akzeptierten Antwort führt dies zu eindeutigen Schlüsselverletzungen, wenn zwei Prozesse wiederholt
upsert_foo
gleichzeitig aufgerufen werden .Eureka! Ich habe in einer Abfrage einen Weg gefunden, dies zu tun: Verwenden Sie diese Option, um
UPDATE ... RETURNING
zu testen, ob Zeilen betroffen sind:Das
UPDATE
muss in einem gesonderten Verfahren durchgeführt werden , denn leider ist dies ein Syntaxfehler ist:Jetzt funktioniert es wie gewünscht:
quelle