Wie aktualisiere ich eine Zeile in einer Tabelle oder füge sie ein, wenn sie nicht vorhanden ist?

83

Ich habe die folgende Tabelle von Zählern:

CREATE TABLE cache (
    key text PRIMARY KEY,
    generation int
);

Ich möchte einen der Zähler erhöhen oder auf Null setzen, wenn die entsprechende Zeile noch nicht vorhanden ist. Gibt es eine Möglichkeit, dies ohne Parallelitätsprobleme in Standard-SQL zu tun? Die Operation ist manchmal Teil einer Transaktion, manchmal getrennt.

Wenn möglich, muss SQL unter SQLite, PostgreSQL und MySQL unverändert ausgeführt werden.

Eine Suche ergab mehrere Ideen, die entweder unter Parallelitätsproblemen leiden oder für eine Datenbank spezifisch sind:

  • Versuchen Sie es mit INSERTeiner neuen Zeile, und UPDATEwenn ein Fehler aufgetreten ist. Leider INSERTbricht der Fehler bei die aktuelle Transaktion ab.

  • UPDATEdie Zeile, und wenn keine Zeilen geändert wurden, INSERTeine neue Zeile.

  • MySQL hat eine ON DUPLICATE KEY UPDATEKlausel.

EDIT: Danke für all die tollen Antworten. Es sieht so aus, als ob Paul Recht hat, und es gibt keinen einzigen tragbaren Weg, dies zu tun. Das ist für mich ziemlich überraschend, da es sich nach einer sehr einfachen Operation anhört.

Remy Blank
quelle
6
Sie werden keine einzige Lösung finden, die für all diese RDBMS funktioniert. Es tut uns leid.
Paul Tomblin
1
Mögliches Duplikat von SQLite - UPSERT * nicht * INSERT oder REPLACE
PearsonArtPhoto
Mögliches Duplikat der Lösungen für INSERT OR UPDATE auf SQL Server
Jonathan Leffler

Antworten:

137

MySQL (und anschließend SQLite) unterstützt auch die REPLACE INTO-Syntax:

REPLACE INTO my_table (pk_id, col1) VALUES (5, '123');

Dadurch wird der Primärschlüssel automatisch identifiziert und eine passende Zeile zum Aktualisieren gefunden. Wenn keine gefunden wird, wird eine neue eingefügt.

andygeers
quelle
13
Um genau zu sein, führt MySQLs REPLACE immer eine Einfügung durch, löscht jedoch zuerst die Zeile, wenn sie bereits vorhanden ist. dev.mysql.com/doc/refman/4.1/en/replace.html
Evan
89
Es ist wichtig zu verstehen, dass es sich um ein Einfügen + Löschen und niemals um ein Update handelt. Dies hat zur Folge, dass Sie beim Ersetzen immer sicherstellen möchten, dass Sie immer Daten für alle Felder angeben.
Zoredache
2
@Zoredache Technisch gesehen ist es ein "Löschen, dann Einfügen", da ein (n) "Einfügen + Löschen" praktisch dasselbe ist wie ein Löschen, aber das spaltet Haare.
Agi Hammerthief
2
@Agihammerthief Es gibt einen sehr realen Unterschied, nämlich dass die neu eingefügte Zeile NICHT denselben Primärschlüssel hat wie die gelöschte Zeile. Bei ON DUPLICATE handelt es sich um denselben Primärschlüssel (sofern Sie ihn nicht ausdrücklich ändern).
Tim Strijdhorst
32

SQLite unterstützt das Ersetzen einer Zeile, falls diese bereits vorhanden ist:

INSERT OR REPLACE INTO [...blah...]

Sie können dies auf kürzen

REPLACE INTO [...blah...]

Diese Verknüpfung wurde hinzugefügt, um mit dem MySQL- REPLACE INTOAusdruck kompatibel zu sein .

Kyle Cronin
quelle
Es erfordert, dass Sie PRAMARY KEYin Ihren Werten ein definiert haben.
DawnSong
24

Ich würde so etwas machen:

INSERT INTO cache VALUES (key, generation)
ON DUPLICATE KEY UPDATE (key = key, generation = generation + 1);

Setzen Sie den Generierungswert im Code oder in SQL auf 0, verwenden Sie jedoch ON DUP ..., um den Wert zu erhöhen. Ich denke, das ist sowieso die Syntax.

jmoz
quelle
1
Diese Antwort sollte ehrlich gesagt die ausgewählte Antwort sein. Es ist eine zerstörungsfreie Bearbeitung, wenn Sie nicht alle Felder an einen Eintrag senden.
Jordanien
9

Die ON DUPLICATE KEY UPDATE-Klausel ist die beste Lösung, weil: REPLACE ein DELETE gefolgt von einem INSERT ausführt, sodass der Datensatz für einen sehr kurzen Zeitraum entfernt wird, wodurch die geringste Wahrscheinlichkeit entsteht, dass eine Abfrage zurückkommt, wenn die Seite übersprungen wurde wird während der REPLACE-Abfrage angezeigt.

Aus diesem Grund bevorzuge ich INSERT ... ON DUPLICATE UPDATE.

Die Lösung von jmoz ist die beste: obwohl ich die SET-Syntax den Klammern vorziehe

INSERT INTO cache 
SET key = 'key', generation = 'generation'
ON DUPLICATE KEY 
UPDATE key = 'key', generation = (generation + 1)
;
Feuerkrähe
quelle
4
REPLACE ist atomar, es gibt also keinen Zeitraum, in dem die Zeile nicht existiert.
Brilliand
5

In PostgreSQL gibt es keinen Zusammenführungsbefehl, und das Schreiben ist nicht trivial - es gibt tatsächlich seltsame Randfälle, die die Aufgabe "interessant" machen.

Der beste Ansatz (wie in: Arbeiten unter den bestmöglichen Bedingungen) ist die Verwendung einer Funktion - wie sie im Handbuch (merge_db) gezeigt wird.

Wenn Sie die Funktion nicht verwenden möchten, können Sie normalerweise Folgendes tun :

updated = db.execute(UPDATE ... RETURNING 1)
if (!updated)
  db.execute(INSERT...)

Denken Sie daran, dass es nicht fehlerfrei ist und es auch wird fehlschlagen wird.


quelle
4

Standard SQL stellt die MERGE-Anweisung für diese Aufgabe bereit. Nicht alle DBMS unterstützen die MERGE-Anweisung.

Jonathan Leffler
quelle
0

Wenn Sie keine übliche Methode zum atomaren Aktualisieren oder Einfügen haben (z. B. über eine Transaktion), können Sie auf ein anderes Sperrschema zurückgreifen. Eine 0-Byte-Datei, ein Systemmutex, eine Named Pipe usw.

Shea
quelle
0

Könnten Sie einen Insert-Trigger verwenden? Wenn dies fehlschlägt, führen Sie ein Update durch.

Michael Todd
quelle
Der Trigger (zumindest in PostgreSQL) wird ausgeführt, wenn der Befehl ausgeführt wurde. Das heißt, Sie können keinen Trigger haben, der ausgeführt wird, wenn der Basisbefehl fehlgeschlagen ist.
0

Wenn Sie eine Bibliothek verwenden können, die SQL für Sie schreibt, können Sie Upsert verwenden (derzeit nur Ruby und Python):

Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
Pet.upsert({:name => 'Jerry'}, :color => 'brown')

Das funktioniert in MySQL, Postgres und SQLite3.

Es schreibt eine gespeicherte Prozedur oder eine benutzerdefinierte Funktion (UDF) in MySQL und Postgres. Es wird INSERT OR REPLACEin SQLite3 verwendet.

Seamus Abshere
quelle