EINFÜGEN, WENN KEIN ANDERES UPDATE EXISTIERT?

275

Ich habe einige "wäre" -Lösungen für den Klassiker "Wie füge ich einen neuen Datensatz ein oder aktualisiere einen, wenn er bereits vorhanden ist" gefunden, aber ich kann keine davon in SQLite zum Laufen bringen.

Ich habe eine Tabelle wie folgt definiert:

CREATE TABLE Book 
ID     INTEGER PRIMARY KEY AUTOINCREMENT,
Name   VARCHAR(60) UNIQUE,
TypeID INTEGER,
Level  INTEGER,
Seen   INTEGER

Ich möchte einen Datensatz mit einem eindeutigen Namen hinzufügen. Wenn der Name bereits vorhanden ist, möchte ich die Felder ändern.

Kann mir bitte jemand sagen, wie das geht?

SparkyNZ
quelle
3
"Einfügen oder Ersetzen" unterscheidet sich grundlegend von "Einfügen oder Aktualisieren"
Fattie

Antworten:

320

Schauen Sie sich http://sqlite.org/lang_conflict.html an .

Du willst so etwas wie:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values
((select ID from Book where Name = "SearchName"), "SearchName", ...);

Beachten Sie, dass jedes Feld, das nicht in der Einfügeliste enthalten ist, auf NULL gesetzt wird, wenn die Zeile bereits in der Tabelle vorhanden ist. Aus diesem Grund gibt es eine Unterauswahl für die IDSpalte: Im Ersatzfall würde die Anweisung sie auf NULL setzen und dann würde eine neue ID zugewiesen.

Dieser Ansatz kann auch verwendet werden, wenn Sie bestimmte Feldwerte in Ruhe lassen möchten, wenn die Zeile im Ersetzungsfall, aber das Feld im Einfügefall auf NULL gesetzt wird.

Angenommen, Sie möchten in SeenRuhe lassen:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values (
   (select ID from Book where Name = "SearchName"),
   "SearchName",
    5,
    6,
    (select Seen from Book where Name = "SearchName"));
janm
quelle
109
Falsches "Einfügen oder Ersetzen" unterscheidet sich von "Einfügen oder Aktualisieren". Eine gültige Antwort finden Sie unter stackoverflow.com/questions/418898/…
rds
12
@rds Nein, es ist nicht falsch, da diese Frage "Felder ändern" lautet und der Primärschlüssel nicht Teil der Spaltenliste ist, aber alle anderen Felder. Wenn Sie Eckfälle haben, in denen Sie nicht alle Feldwerte ersetzen, oder wenn Sie mit dem Primärschlüssel herumspielen, sollten Sie etwas anderes tun. Wenn Sie einen vollständigen Satz neuer Felder haben, ist dieser Ansatz in Ordnung. Haben Sie ein bestimmtes Problem, das ich nicht sehen kann?
Januar
9
Es ist gültig, wenn Sie den gesamten neuen Wert für alle Felder kennen. Wenn der Benutzer beispielsweise nur das aktualisiert, Levelkann dieser Ansatz nicht befolgt werden.
14.
4
Richtig . Die andere Antwort ist relevant, aber dieser Ansatz gilt für die Aktualisierung aller Felder (mit Ausnahme des Schlüssels).
Ярослав Рахматуллин
2
Ja, das ist völlig falsch. Was passiert, wenn ich nur den Wert einer einzelnen Spalte für Confliction von einer neuen aktualisieren möchte? Im obigen Fall werden alle anderen Daten durch neue ersetzt, die nicht korrekt sind.
Mrug
85

Sie sollten den INSERT OR IGNOREBefehl gefolgt von einem UPDATEBefehl verwenden: Im folgenden Beispiel nameist ein Primärschlüssel:

INSERT OR IGNORE INTO my_table (name, age) VALUES ('Karen', 34)
UPDATE my_table SET age = 34 WHERE name='Karen'

Der erste Befehl fügt den Datensatz ein. Wenn der Datensatz vorhanden ist, wird der Fehler ignoriert, der durch den Konflikt mit einem vorhandenen Primärschlüssel verursacht wurde.

Der zweite Befehl aktualisiert den Datensatz (der jetzt definitiv existiert).

Moshik
quelle
6
In welchem ​​Moment wird es ignoriert? Wann sind Name und Alter gleich?
Mou
Dies sollte die Lösung sein ... Wenn Sie beim Einfügen einen Auslöser verwenden, wird die akzeptierte Antwort jedes Mal ausgelöst. Dies führt nicht und führt nur ein Update durch
PodTech.io
1
Es wird nur aufgrund des Namens ignoriert. Denken Sie daran, dass nur die Spalte "Name" ein Primärschlüssel ist.
Gabriel Ferrer
3
Wenn der Datensatz neu ist, ist das Update nicht erforderlich, wird aber trotzdem ausgeführt, was zu einer schlechten Leistung führt.
MarcG
Wie führe ich eine vorbereitete Anweisung dafür aus?
prashant
65

Sie müssen eine Einschränkung für die Tabelle festlegen, um einen " Konflikt " auszulösen, den Sie dann durch Ersetzen lösen:

CREATE TABLE data   (id INTEGER PRIMARY KEY, event_id INTEGER, track_id INTEGER, value REAL);
CREATE UNIQUE INDEX data_idx ON data(event_id, track_id);

Dann können Sie Folgendes ausgeben:

INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 2, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 5);

Die "SELECT * FROM Daten" geben Ihnen:

2|2|2|3.0
3|1|2|5.0

Beachten Sie, dass die data.id "3" und nicht "1" ist, da REPLACE ein DELETE und INSERT ausführt, kein UPDATE. Dies bedeutet auch, dass Sie sicherstellen müssen, dass Sie alle erforderlichen Spalten definieren, da sonst unerwartete NULL-Werte angezeigt werden.

Gaspard
quelle
33

Aktualisieren Sie es zuerst. Wenn die Anzahl der betroffenen Zeilen = 0 ist, fügen Sie sie ein. Es ist das einfachste und für alle RDBMS geeignete .

Burçin
quelle
11
Unabhängig von der Datenbank sollten zwei Vorgänge mit einer Transaktion auf der richtigen Isolationsstufe kein Problem darstellen.
Januar
5
Insert or Replaceist wirklich vorzuziehen.
MPelletier
1
Ich wünschte wirklich, die IT-Dokumentation würde mehr Beispiele enthalten. Ich habe dies unten versucht und es funktioniert nicht (meine Syntax ist offensichtlich falsch). Irgendwelche Ideen, was es sein sollte? INSERT INTO Book (Name, TypeID, Level, Seen) VALUES ('Superman', '2', '14', '0') ON CONFLICT REPLACE Book (Name, TypeID, Level, Seen) VALUES ('Superman', ' 2 ',' 14 ',' 0 ')
SparkyNZ
6
Was ist auch, wenn die Zeilenwerte genau gleich sind, die Anzahl der betroffenen Zeilen Null ist und eine neue doppelte Zeile erstellt wird.
Barkside
2
+1, weil INSERT OR REPLACE die ursprüngliche Zeile bei einem Konflikt löscht und wenn Sie nicht alle Spalten festlegen, verlieren Sie die ursprünglichen Werte
Pavel Machyniak
20

INSERT OR REPLACE ersetzt die anderen Felder auf den Standardwert.

sqlite> CREATE TABLE Book (
  ID     INTEGER PRIMARY KEY AUTOINCREMENT,
  Name   TEXT,
  TypeID INTEGER,
  Level  INTEGER,
  Seen   INTEGER
);

sqlite> INSERT INTO Book VALUES (1001, 'C++', 10, 10, 0);
sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR REPLACE INTO Book(ID, Name) VALUES(1001, 'SQLite');

sqlite> SELECT * FROM Book;
1001|SQLite|||

Wenn Sie das andere Feld beibehalten möchten

sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR IGNORE INTO Book(ID) VALUES(1001);
sqlite> UPDATE Book SET Name='SQLite' WHERE ID=1001;

sqlite> SELECT * FROM Book;
1001|SQLite|10|10|0

oder mit UPSERT (Syntax wurde SQLite mit Version 3.24.0 (2018-06-04) hinzugefügt)

INSERT INTO Book (ID, Name)
  VALUES (1001, 'SQLite')
  ON CONFLICT (ID) DO
  UPDATE SET Name=excluded.Name;

Das excluded.Präfix entspricht dem Wert in VALUES.

Steely Wing
quelle
16

Upsert ist was du willst. UPSERTDie Syntax wurde zu SQLite mit Version 3.24.0 (2018-06-04) hinzugefügt.

CREATE TABLE phonebook2(
  name TEXT PRIMARY KEY,
  phonenumber TEXT,
  validDate DATE
);

INSERT INTO phonebook2(name,phonenumber,validDate)
  VALUES('Alice','704-555-1212','2018-05-08')
  ON CONFLICT(name) DO UPDATE SET
    phonenumber=excluded.phonenumber,
    validDate=excluded.validDate
  WHERE excluded.validDate>phonebook2.validDate;

Seien Sie gewarnt, dass an dieser Stelle das eigentliche Wort "UPSERT" nicht Teil der Upsert-Syntax ist.

Die richtige Syntax lautet

INSERT INTO ... ON CONFLICT(...) DO UPDATE SET...

und wenn Sie INSERT INTO SELECT ...Ihre Auswahl treffen, müssen Sie zumindest WHERE truedie Parser-Mehrdeutigkeit bezüglich des Tokens ONmit der Join-Syntax lösen .

Seien Sie gewarnt, dass INSERT OR REPLACE...der Datensatz vor dem Einfügen eines neuen Datensatzes gelöscht wird, wenn er ersetzt werden muss. Dies kann schlecht sein, wenn Sie Fremdschlüsselkaskaden oder andere Löschauslöser haben.

Genosse Joecool
quelle
Es existiert in der Dokumentation und ich habe die neueste Version von SQLite, nämlich 3.25.1, aber es funktioniert nicht für mich
Bentaiba Miled Basma
Haben Sie diese beiden Abfragen oben wörtlich ausprobiert?
ComradeJoecool
Beachten Sie, dass Ubuntu 18.04 mit SQLite3 v3.22 und nicht mit 3.25 geliefert wird und daher die UPSERTSyntax nicht unterstützt .
Starbeamrainbowlabs
Vielen Dank für die Referenzversion der Funktion!
Matteo
6

Wenn Sie keinen Primärschlüssel haben, können Sie ihn einfügen, falls er nicht vorhanden ist, und dann ein Update durchführen. Die Tabelle muss mindestens einen Eintrag enthalten, bevor Sie diesen verwenden können.

INSERT INTO Test 
   (id, name)
   SELECT 
      101 as id, 
      'Bob' as name
   FROM Test
       WHERE NOT EXISTS(SELECT * FROM Test WHERE id = 101 and name = 'Bob') LIMIT 1;

Update Test SET id='101' WHERE name='Bob';
matt
quelle
Dies ist die einzige Lösung, die bei mir funktioniert hat, ohne doppelte Einträge zu erstellen. Wahrscheinlich, weil die von mir verwendete Tabelle keine Primärschlüssel und 2 Spalten ohne Standardwerte enthält. Obwohl es sich um eine langwierige Lösung handelt, erledigt es die Aufgabe ordnungsgemäß und funktioniert wie erwartet.
Inbar Rose
4

Ich glaube du willst UPSERT .

"EINFÜGEN ODER ERSETZEN" ohne den zusätzlichen Trick in dieser Antwort setzt alle Felder, die Sie nicht angeben, auf NULL oder einen anderen Standardwert zurück. (Dieses Verhalten von INSERT OR REPLACE unterscheidet sich von UPDATE. Es ist genau wie INSERT, da es tatsächlich INSERT ist. Wenn Sie jedoch UPDATE-if-exist möchten, möchten Sie wahrscheinlich die UPDATE-Semantik und werden vom tatsächlichen Ergebnis unangenehm überrascht sein.)

Der Trick bei der vorgeschlagenen UPSERT-Implementierung besteht im Wesentlichen darin, INSERT OR REPLACE zu verwenden, aber alle Felder mit eingebetteten SELECT-Klauseln anzugeben, um den aktuellen Wert für Felder abzurufen, die Sie nicht ändern möchten.

Metamatt
quelle
1

Ich denke, es ist erwähnenswert, dass es hier zu unerwartetem Verhalten kommen kann, wenn Sie nicht genau verstehen, wie PRIMARY KEY und UNIQUE interagieren.

Wenn Sie beispielsweise einen Datensatz nur einfügen möchten, wenn das Feld NAME derzeit nicht verwendet wird, und wenn dies der Fall ist, möchten Sie, dass eine Einschränkungsausnahme ausgelöst wird , wird INSERT OR REPLACE keine Ausnahme auslösen und stattdessen Lösen Sie die EINZIGARTIGE Einschränkung selbst, indem Sie den widersprüchlichen Datensatz (den vorhandenen Datensatz mit demselben NAMEN ) ersetzen . Gaspard's zeigt dies sehr gut in seiner obigen Antwort .

Wenn eine Einschränkungsausnahme ausgelöst werden soll, müssen Sie eine INSERT- Anweisung verwenden und sich auf einen separaten UPDATE- Befehl verlassen, um den Datensatz zu aktualisieren, sobald Sie wissen, dass der Name nicht verwendet wird.

Matt Matthias
quelle