Ich habe das folgende UPSERT in PostgreSQL 9.5:
INSERT INTO chats ("user", "contact", "name")
VALUES ($1, $2, $3),
($2, $1, NULL)
ON CONFLICT("user", "contact") DO NOTHING
RETURNING id;
Wenn es keine Konflikte gibt, wird Folgendes zurückgegeben:
----------
| id |
----------
1 | 50 |
----------
2 | 51 |
----------
Bei Konflikten werden jedoch keine Zeilen zurückgegeben:
----------
| id |
----------
Ich möchte die neuen id
Spalten zurückgeben, wenn keine Konflikte vorliegen, oder die vorhandenen id
Spalten der widersprüchlichen Spalten zurückgeben.
Kann das gemacht werden? Wenn ja, wie?
ON CONFLICT UPDATE
damit die Zeile geändert wird. DannRETURNING
wird es erfasst.Antworten:
Ich hatte genau das gleiche Problem und löste es mit "Update durchführen" anstelle von "Nichts tun", obwohl ich nichts zu aktualisieren hatte. In Ihrem Fall wäre es ungefähr so:
Diese Abfrage gibt alle Zeilen zurück, unabhängig davon, ob sie gerade eingefügt wurden oder bereits vorhanden waren.
quelle
DO NOTHING
Aspekt der ursprünglichen Frage zu erreichen - für mich scheint sie das konfliktfreie Feld (hier "Name") für alle Zeilen zu aktualisieren.Die derzeit akzeptierte Antwort scheint für ein einzelnes Konfliktziel, wenige Konflikte, kleine Tupel und keine Auslöser in Ordnung zu sein. Es vermeidet Parallelitätsproblem 1 (siehe unten) mit Brute Force. Die einfache Lösung hat ihren Reiz, die Nebenwirkungen können weniger wichtig sein.
In allen anderen Fällen aktualisieren Sie identische Zeilen jedoch nicht ohne Notwendigkeit. Auch wenn Sie an der Oberfläche keinen Unterschied sehen, gibt es verschiedene Nebenwirkungen :
Es können Auslöser ausgelöst werden, die nicht ausgelöst werden sollten.
Es sperrt "unschuldige" Zeilen, was möglicherweise Kosten für gleichzeitige Transaktionen verursacht.
Die Zeile wird möglicherweise neu angezeigt, obwohl sie alt ist (Transaktionszeitstempel).
Am wichtigsten ist , dass mit dem MVCC-Modell von PostgreSQL für jeden eine neue Zeilenversion geschrieben wird
UPDATE
, unabhängig davon, ob sich die Zeilendaten geändert haben. Dies führt zu einer Leistungsstrafe für das UPSERT selbst, zum Aufblähen der Tabelle, zum Aufblähen des Index, zur Leistungsstrafe für nachfolgende Operationen auf dem Tisch und zu denVACUUM
Kosten. Ein kleiner Effekt für wenige Duplikate, aber massiv für meistens Dupes.Außerdem ist es manchmal nicht praktisch oder sogar möglich, es zu verwenden
ON CONFLICT DO UPDATE
. Das Handbuch:Ein einzelnes "Konfliktziel" ist nicht möglich, wenn mehrere Indizes / Einschränkungen beteiligt sind.
Sie können (fast) dasselbe ohne leere Updates und Nebenwirkungen erreichen. Einige der folgenden Lösungen funktionieren auch mit
ON CONFLICT DO NOTHING
(kein "Konfliktziel"), um alle möglichen Konflikte zu erfassen , die auftreten können - was wünschenswert sein kann oder nicht.Ohne gleichzeitiges Schreiben
Die
source
Spalte ist eine optionale Ergänzung, um zu demonstrieren, wie dies funktioniert. Möglicherweise benötigen Sie es tatsächlich, um den Unterschied zwischen beiden Fällen zu erkennen (ein weiterer Vorteil gegenüber leeren Schreibvorgängen).Das Finale
JOIN chats
funktioniert, da neu eingefügte Zeilen aus einem angehängten datenmodifizierenden CTE in der zugrunde liegenden Tabelle noch nicht sichtbar sind. (Alle Teile derselben SQL-Anweisung sehen dieselben Snapshots der zugrunde liegenden Tabellen.)Da der
VALUES
Ausdruck freistehend ist (nicht direkt an einen angehängtINSERT
), kann Postgres keine Datentypen aus den Zielspalten ableiten, und Sie müssen möglicherweise explizite Typumwandlungen hinzufügen. Das Handbuch:Die Abfrage selbst (ohne Berücksichtigung der Nebenwirkungen) kann für einige Dupes aufgrund des Overheads des CTE und der zusätzlichen Abfrage etwas teurer sein
SELECT
(was billig sein sollte, da der perfekte Index per Definition vorhanden ist - eine eindeutige Einschränkung wird mit implementiert ein Index).Kann für viele Duplikate (viel) schneller sein . Die effektiven Kosten für zusätzliche Schreibvorgänge hängen von vielen Faktoren ab.
Aber es gibt auf jeden Fall weniger Nebenwirkungen und versteckte Kosten . Es ist höchstwahrscheinlich insgesamt billiger.
Angehängte Sequenzen sind noch weit fortgeschritten, da vor dem Testen auf Konflikte Standardwerte eingegeben werden .
Über CTEs:
Bei gleichzeitiger Schreiblast
Standardtransaktionsisolation
READ COMMITTED
annehmen . Verbunden:Die beste Strategie zur Abwehr von Rennbedingungen hängt von den genauen Anforderungen, der Anzahl und Größe der Zeilen in der Tabelle und in den UPSERTs, der Anzahl der gleichzeitigen Transaktionen, der Wahrscheinlichkeit von Konflikten, den verfügbaren Ressourcen und anderen Faktoren ab.
Parallelitätsproblem 1
Wenn eine gleichzeitige Transaktion in eine Zeile geschrieben wurde, die Ihre Transaktion jetzt zu UPSERT versucht, muss Ihre Transaktion warten, bis die andere abgeschlossen ist.
Wenn die andere Transaktion mit
ROLLBACK
(oder einem Fehler, dh automatischROLLBACK
) endet , kann Ihre Transaktion normal fortgesetzt werden. Geringfügige mögliche Nebenwirkung: Lücken in fortlaufenden Nummern. Aber keine fehlenden Zeilen.Wenn die andere Transaktion normal endet (implizit oder explizit
COMMIT
),INSERT
erkennen Sie einen Konflikt (derUNIQUE
Index / die Einschränkung ist absolut) und gebenDO NOTHING
daher auch die Zeile nicht zurück. (Kann die Zeile auch nicht sperren, wie in Problem 2 der Parallelität unten gezeigt, da sie nicht sichtbar ist .) DerSELECT
sieht denselben Snapshot vom Beginn der Abfrage an und kann auch die noch unsichtbare Zeile nicht zurückgeben.Solche Zeilen fehlen in der Ergebnismenge (obwohl sie in der zugrunde liegenden Tabelle vorhanden sind)!
Dies kann in Ordnung sein, wie es ist . Insbesondere, wenn Sie keine Zeilen wie im Beispiel zurückgeben und zufrieden sind, dass die Zeile vorhanden ist. Wenn das nicht gut genug ist, gibt es verschiedene Möglichkeiten, dies zu umgehen.
Sie können die Zeilenanzahl der Ausgabe überprüfen und die Anweisung wiederholen, wenn sie nicht mit der Zeilenanzahl der Eingabe übereinstimmt. Kann für den seltenen Fall gut genug sein. Der Punkt ist, eine neue Abfrage zu starten (kann sich in derselben Transaktion befinden), in der dann die neu festgeschriebenen Zeilen angezeigt werden.
Oder überprüfen Ergebniszeilen für fehlende innerhalb derselben Abfrage und überschreibt die mit dem Brute - Force - Trick in demonstriert Alextoni Antwort .
Es ist wie bei der obigen Abfrage, aber wir fügen dem CTE noch einen Schritt hinzu
ups
, bevor wir die vollständige Ergebnismenge zurückgeben. Dieser letzte CTE wird die meiste Zeit nichts tun. Nur wenn Zeilen im zurückgegebenen Ergebnis verloren gehen, wenden wir Brute Force an.Noch mehr Aufwand. Je mehr Konflikte mit bereits vorhandenen Zeilen auftreten, desto wahrscheinlicher ist es, dass dies den einfachen Ansatz übertrifft.
Ein Nebeneffekt: Das 2. UPSERT schreibt Zeilen in unregelmäßiger Reihenfolge, sodass die Möglichkeit von Deadlocks (siehe unten) wieder eingeführt wird, wenn sich drei oder mehr Transaktionen, die in dieselben Zeilen schreiben, überlappen. Wenn das ein Problem ist, brauchen Sie eine andere Lösung - wie das Wiederholen der gesamten Aussage wie oben erwähnt.
Parallelitätsproblem 2
Wenn gleichzeitige Transaktionen in betroffene Spalten betroffener Zeilen schreiben können und Sie sicherstellen müssen, dass die gefundenen Zeilen zu einem späteren Zeitpunkt in derselben Transaktion noch vorhanden sind, können Sie vorhandene Zeilen im CTE kostengünstig sperren
ins
(die sonst entsperrt würden). mit:Und fügen Sie ein in die Verriegelungs Klausel
SELECT
als auch, wieFOR UPDATE
.Dadurch warten konkurrierende Schreibvorgänge bis zum Ende der Transaktion, wenn alle Sperren aufgehoben sind. Also sei kurz.
Weitere Details und Erklärungen:
Deadlocks?
Verteidigen Sie sich gegen Deadlocks, indem Sie Zeilen in konsistenter Reihenfolge einfügen . Sehen:
Datentypen und Casts
Vorhandene Tabelle als Vorlage für Datentypen ...
Explizite Typumwandlungen für die erste Datenzeile im freistehenden
VALUES
Ausdruck können unpraktisch sein. Es gibt Möglichkeiten, dies zu umgehen. Sie können jede vorhandene Beziehung (Tabelle, Ansicht, ...) als Zeilenvorlage verwenden. Die Zieltabelle ist die offensichtliche Wahl für den Anwendungsfall. Eingabedaten werden automatisch zu geeigneten Typen gezwungen, wie in derVALUES
Klausel einesINSERT
:Dies funktioniert bei einigen Datentypen nicht. Sehen:
... und Namen
Dies funktioniert auch für alle Datentypen.
Beim Einfügen in alle (führenden) Spalten der Tabelle können Sie Spaltennamen weglassen. Angenommen, die Tabelle
chats
im Beispiel besteht nur aus den 3 im UPSERT verwendeten Spalten:Nebenbei: Verwenden Sie keine reservierten Wörter wie
"user"
als Bezeichner. Das ist eine geladene Fußwaffe. Verwenden Sie legale, nicht zitierte Bezeichner in Kleinbuchstaben. Ich habe es durch ersetztusr
.quelle
ON CONFLICT SELECT...
wo etwas :)Upsert, eine Erweiterung der
INSERT
Abfrage zu sein, kann im Falle eines Einschränkungskonflikts mit zwei verschiedenen Verhaltensweisen definiert werden:DO NOTHING
oderDO UPDATE
.Beachten Sie auch, dass
RETURNING
nichts zurückgegeben wird, da keine Tupel eingefügt wurden . MitDO UPDATE
ist es nun möglich, Operationen an dem Tupel auszuführen, mit dem ein Konflikt besteht. Beachten Sie zunächst, dass es wichtig ist, eine Einschränkung zu definieren, anhand derer definiert wird, dass ein Konflikt vorliegt.quelle
Für das Einfügen eines einzelnen Elements würde ich bei der Rückgabe der ID wahrscheinlich eine Koaleszenz verwenden:
quelle
Der Hauptzweck der Verwendung
ON CONFLICT DO NOTHING
besteht darin, Fehler zu vermeiden, aber es werden keine Zeilenrückgaben verursacht. Also brauchen wir noch einenSELECT
, um die vorhandene ID zu erhalten.Wenn es in dieser SQL bei Konflikten fehlschlägt, wird nichts zurückgegeben, und die zweite
SELECT
Zeile erhält die vorhandene Zeile. Wenn es erfolgreich eingefügt wird, gibt es zwei gleiche Datensätze, dann müssen wirUNION
das Ergebnis zusammenführen.quelle
Ich habe die erstaunliche Antwort von Erwin Brandstetter geändert, die die Sequenz nicht erhöht und auch keine Zeilen schreibgeschützt. Ich bin relativ neu in PostgreSQL. Bitte lassen Sie mich wissen, wenn Sie Nachteile dieser Methode feststellen:
Dies setzt voraus, dass die Tabelle
chats
eine eindeutige Einschränkung für Spalten aufweist(usr, contact)
.Update: Die vorgeschlagenen Überarbeitungen von Spatar (unten) wurden hinzugefügt . Vielen Dank!
quelle
CASE WHEN r.id IS NULL THEN FALSE ELSE TRUE END AS row_exists
nur zu schreibenr.id IS NOT NULL as row_exists
. AnstattWHERE row_exists=FALSE
nur zu schreibenWHERE NOT row_exists
.