Wie emuliere ich "Insert Ignorieren" und "On Duplicate Key Update" (SQL Merge) mit postgresql?

140

Einige SQL Server verfügen über eine Funktion, die INSERTübersprungen wird, wenn sie eine Primär- / eindeutige Schlüsselbeschränkung verletzt. Zum Beispiel hat MySQL INSERT IGNORE.

Was ist der beste Weg, um INSERT IGNOREund ON DUPLICATE KEY UPDATEmit PostgreSQL zu emulieren ?

gpilotino
quelle
Siehe auch: stackoverflow.com/questions/5269590/…
Dave Jarvis
6
es ist als 9,5, möglich nativ: stackoverflow.com/a/34639631/4418
warren
Das Emulieren von MySQL: Unter ON DUPLICATE KEY UPDATEPgSQL 9.5 ist dies immer noch nicht möglich, da ON CLAUSESie für das PgSQL- Äquivalent den Namen der Einschränkung angeben müssen, während MySQL jede Einschränkung erfassen kann, ohne sie definieren zu müssen. Dies verhindert, dass ich diese Funktion "emuliere", ohne Abfragen neu zu schreiben.
NeverEndingQueue

Antworten:

35

Versuchen Sie ein UPDATE durchzuführen. Wenn keine Zeile geändert wird, die bedeutet, dass sie nicht vorhanden ist, fügen Sie sie ein. Offensichtlich tun Sie dies innerhalb einer Transaktion.

Sie können dies natürlich in eine Funktion einbinden, wenn Sie den zusätzlichen Code nicht auf der Clientseite platzieren möchten. Sie brauchen auch eine Schleife für die sehr seltene Rennbedingung in diesem Denken.

Ein Beispiel hierfür finden Sie in der Dokumentation: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html , Beispiel 40-2 ganz unten.

Das ist normalerweise der einfachste Weg. Sie können mit Regeln etwas zaubern, aber es wird wahrscheinlich viel chaotischer. Ich würde den Wrap-in-Function-Ansatz jeden Tag empfehlen.

Dies funktioniert für einzelne oder wenige Zeilenwerte. Wenn Sie beispielsweise mit einer großen Anzahl von Zeilen aus einer Unterabfrage arbeiten, teilen Sie diese am besten in zwei Abfragen auf, eine für INSERT und eine für UPDATE (natürlich als geeignete Verknüpfung / Unterauswahl - Sie müssen Ihre Hauptabfrage nicht schreiben zweimal filtern)

Magnus Hagander
quelle
4
"Wenn Sie mit großen Zeilenmengen zu tun haben", ist das genau mein Fall. Ich möchte Zeilen massenweise aktualisieren / einfügen und mit MySQL kann ich dies mit nur EINER Abfrage ohne Schleife tun. Jetzt frage ich mich, ob dies auch mit postgresql möglich ist: nur eine Abfrage zum Massenaktualisieren ODER Einfügen verwenden. Sie sagen: "Sie teilen es am besten in zwei Abfragen auf, eine für INSERT und eine für UPDATE", aber wie kann ich eine Einfügung durchführen, die keine Fehler auf doppelte Schlüssel wirft? (dh "INSERT IGNORE")
gpilotino
4
Magnus bedeutete, dass Sie eine Abfrage wie die folgende verwenden: "Transaktion starten; temporäre Tabelle temporäre Tabelle als Auswahl * aus Test erstellen, wo falsch; temporäre Tabelle aus 'data_file.csv' kopieren; Tabellentest sperren; Testsatzdaten aktualisieren = temporäre_Tabelle.Daten aus temporäre_Tabelle aktualisieren wo test.id = temporäre_Tabelle.id; in Test einfügen select * from temporäre_Tabelle wo ID nicht in (ID aus Test auswählen) als "
Tometzky
25
Update: Mit PostgreSQL 9.5 ist dies jetzt so einfach wie INSERT ... ON CONFLICT DO NOTHING;. Siehe auch Antwort stackoverflow.com/a/34639631/2091700 .
Alphaaa
Wichtig, SQL-Standard MERGEist kein sicherer Upsert für Parallelität, es sei denn, Sie nehmen einen LOCK TABLEersten. Die Leute benutzen es so, aber es ist falsch.
Craig Ringer
1
Mit v9.5 ist es jetzt eine "native" Funktion. Überprüfen Sie daher bitte den Kommentar von @Alphaaa (nur Werbung für den Kommentar, der die Antwort
ankündigt
178

Mit PostgreSQL 9.5 ist dies nun eine native Funktionalität (wie sie MySQL seit mehreren Jahren hat):

INSERT ... ON CONFLICT NICHTS / AKTUALISIEREN ("UPSERT")

9.5 bietet Unterstützung für "UPSERT" -Operationen. INSERT wird erweitert, um eine ON CONFLICT DO UPDATE / IGNORE-Klausel zu akzeptieren. Diese Klausel gibt eine alternative Maßnahme an, die im Falle eines möglichen doppelten Verstoßes zu ergreifen ist.

...

Ein weiteres Beispiel für eine neue Syntax:

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1) 
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;
Labyrinth
quelle
100

Bearbeiten: Falls Sie die Antwort von Warren verpasst haben , hat PG9.5 diese nun nativ; Zeit für ein Upgrade!


Aufbauend auf der Antwort von Bill Karwin, um darzulegen, wie ein regelbasierter Ansatz aussehen würde (Übertragung von einem anderen Schema in derselben Datenbank und mit einem mehrspaltigen Primärschlüssel):

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";

Hinweis: Die Regel gilt für alle INSERTVorgänge, bis die Regel gelöscht wird, also nicht ganz ad hoc.

EoghanM
quelle
@sema meinst du, wenn another_schema.my_tableDuplikate gemäß den Einschränkungen von my_table?
EoghanM
2
@EoghanM Ich habe die Regel in Postgresql 9.3 getestet und konnte immer noch Duplikate mit mehreren Zeilen einfügen, z. B. INSERT INTO "my_table" (a, b), (a, b); (Angenommen, die Zeile (a, b) existiert noch nicht in "my_table".)
Sema
@sema, gotcha - das muss bedeuten, dass die Regel zu Beginn über alle einzufügenden Daten ausgeführt wird und nicht nach dem Einfügen jeder Zeile erneut ausgeführt wird. Ein Ansatz wäre, Ihre Daten zuerst in eine andere temporäre Tabelle einzufügen, die keine Einschränkungen aufweist, und dannINSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;
EoghanM
@EoghanM Ein anderer Ansatz besteht darin, doppelte Einschränkungen vorübergehend zu lockern und Duplikate beim Einfügen zu akzeptieren, aber Duplikate anschließend mitDELETE 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
Ich habe das von @sema beschriebene Problem. Wenn ich eine Einfügung (a, b), (a, b) mache, wird ein Fehler ausgegeben. Gibt es auch in diesem Fall eine Möglichkeit, die Fehler zu unterdrücken?
Diogo Melo
35

Für diejenigen unter Ihnen, die Postgres 9.5 oder höher haben, sollte die neue Syntax ON CONFLICT DO NOTHING funktionieren:

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;

Für diejenigen von uns, die eine frühere Version haben, funktioniert dieser richtige Join stattdessen:

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
quelle
Der zweite Ansatz funktioniert nicht, wenn in einer gleichzeitigen Umgebung eine große Einfügung vorgenommen wird. Sie erhalten eine, Unique violation: 7 ERROR: duplicate key value violates unique constraintwenn target_tableeine andere Zeile eingefügt wurde, während diese Abfrage ausgeführt wurde, wenn sich ihre Schlüssel tatsächlich gegenseitig duplizieren. Ich glaube, dass das Sperren target_tablehelfen wird, aber die Parallelität wird offensichtlich leiden.
G. Kashtanov
1
ON CONFLICT (field_one) DO NOTHINGist der beste Teil der Antwort.
Abel Callejo
24

Um die Logik zum Ignorieren des Einfügens zu erhalten, können Sie wie folgt vorgehen . Ich fand, dass das Einfügen aus einer select-Anweisung von Literalwerten am besten funktioniert. Dann können Sie die doppelten Schlüssel mit einer NOT EXISTS-Klausel ausblenden. Um das Update auf doppelte Logik zu bekommen, vermute ich, dass eine pl / pgsql-Schleife notwendig wäre.

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
quelle
Was ist, wenn tmp eine doppelte Zeile enthält, was passieren kann?
Henley Chiu
Sie können immer mit dem eindeutigen Schlüsselwort auswählen.
Keyo
5
Genau wie zu Ihrer Information funktioniert der Trick "WO NICHT EXISTIERT" nicht für mehrere Transaktionen, da die verschiedenen Transaktionen die neu hinzugefügten Daten der anderen Transaktionen nicht sehen können.
Dave Johansen
21
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')
user2342158
quelle
Welche Auswirkungen haben mehrere Transaktionen, die alle versuchen, dasselbe zu tun? Ist es möglich, dass zwischen dem Ausführen, wo nicht vorhanden ist, und dem Einfügen, das eine andere Transaktion ausführt, eine Zeile eingefügt wird? Und wenn Postgres dies verhindern kann, führt Postgres dann nicht einen Synchronisationspunkt für alle diese Transaktionen ein, wenn sie dies erreichen?
Καrτhικ
Dies funktioniert nicht mit mehreren Transaktionen, da die neu hinzugefügten Daten für die anderen Transaktionen nicht sichtbar sind.
Dave Johansen
12

Es sieht so aus, als ob PostgreSQL ein Schemaobjekt unterstützt, das als Regel bezeichnet wird .

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

Sie könnten eine Regel erstellen , ON INSERTfür eine bestimmte Tabelle, es tut zu machen , NOTHINGwenn eine Zeile mit dem angegebenen Primärschlüsselwert vorhanden ist , sonst macht es noch ein UPDATEstatt der , INSERTwenn eine Zeile mit dem Primärschlüsselwert gegeben existiert.

Ich habe es selbst nicht versucht, daher kann ich nicht aus Erfahrung sprechen oder ein Beispiel anbieten.

Bill Karwin
quelle
1
Wenn ich es gut verstanden habe, sind diese Regeln Trigger, die jedes Mal ausgeführt werden, wenn eine Anweisung aufgerufen wird. Was ist, wenn ich die Regel nur für eine Abfrage anwenden möchte? Ich muss die Regel erstellen und sie dann sofort zerstören. (Was ist mit den Rennbedingungen?)
GPILOTINO
3
Ja, ich hätte auch die gleichen Fragen. Der Regelmechanismus ist das, was ich in PostgreSQL am ehesten mit MySQLs INSERT IGNORE oder ON DUPLICATE KEY UPDATE finden kann. Wenn wir nach "postgresql on duplicate key update" googeln, finden Sie andere Leute, die den Regelmechanismus empfehlen, obwohl eine Regel für jedes INSERT gelten würde, nicht nur auf Ad-hoc-Basis.
Bill Karwin
4
PostgreSQL unterstützt Transaktions-DDL. Wenn Sie also eine Regel erstellen und innerhalb einer einzelnen Transaktion ablegen, war die Regel außerhalb dieser Transaktion niemals sichtbar (und hatte daher auch außerhalb dieser Transaktion keine Auswirkungen).
CDhowie
6

Wie @hanmari in seinem Kommentar erwähnt. Beim Einfügen in eine Postgres-Tabelle ist der On-Konflikt (..) nichts zu tun der beste Code, um keine doppelten Daten einzufügen:

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

Mit der Codezeile ON CONFLICT kann die insert-Anweisung weiterhin Datenzeilen einfügen. Der Abfrage- und Wertecode ist ein Beispiel für das Einfügen eines Datums aus einem Excel in eine Postgres-DB-Tabelle. Ich habe einer Postgres-Tabelle, die ich verwende, Einschränkungen hinzugefügt, um sicherzustellen, dass das ID-Feld eindeutig ist. Anstatt einen Löschvorgang für dieselben Datenzeilen auszuführen, füge ich eine Zeile SQL-Code hinzu, die die ID-Spalte ab 1 neu nummeriert. Beispiel:

q = 'ALTER id_column serial RESTART WITH 1'

Wenn meine Daten ein ID-Feld haben, verwende ich dieses nicht als primäre ID / serielle ID. Ich erstelle eine ID-Spalte und setze sie auf seriell. Ich hoffe, diese Informationen sind für alle hilfreich. * Ich habe keinen Hochschulabschluss in Softwareentwicklung / -codierung. Alles, was ich im Codieren weiß, lerne ich selbst.

Yankeeownz
quelle
Dies funktioniert nicht bei zusammengesetzten eindeutigen Indizes!
Nulik
4

Diese Lösung vermeidet die Verwendung von Regeln:

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;

Es hat jedoch einen Leistungsnachteil (siehe PostgreSQL.org ):

Ein Block mit einer EXCEPTION-Klausel ist beim Ein- und Aussteigen erheblich teurer als ein Block ohne. Verwenden Sie daher EXCEPTION nicht ohne Notwendigkeit.

Nummer vier
quelle
1

In großen Mengen können Sie die Zeile immer vor dem Einfügen löschen. Das Löschen einer nicht vorhandenen Zeile verursacht keinen Fehler und wird daher sicher übersprungen.

David Noriega
quelle
2
Dieser Ansatz wird ziemlich anfällig für seltsame Rennbedingungen sein, ich würde es nicht empfehlen ...
Steven Schlansker
1
+1 Das ist einfach und allgemein. Bei sorgfältiger Anwendung kann dies tatsächlich eine einfache Lösung sein.
Wouter van Nifterick
1
Es funktioniert auch nicht, wenn die vorhandenen Daten nach dem Einfügen geändert wurden (jedoch nicht auf dem doppelten Schlüssel) und wir die Aktualisierungen beibehalten möchten. Dies ist das Szenario, wenn SQL-Skripte für eine Reihe leicht unterschiedlicher Systeme geschrieben wurden, z. B. Datenbank-Updates, die auf Produktions-, Qualitätssicherungs-, Entwicklungs- und Testsystemen ausgeführt werden.
Hanno Fietz
1
Fremdschlüssel können kein Problem sein, wenn Sie sie mit DEFERRABLE INITIALLY DEFERREDFlags erstellen .
Temoto
-1

Für Datenimport-Skripte gibt es eine etwas umständliche Formulierung, die dennoch funktioniert, um "WENN NICHT EXISTIERT" zu ersetzen:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
analytik_work
quelle