Ich muss UPSERT / INSERT ODER UPDATE für eine SQLite-Datenbank ausführen.
Es gibt den Befehl INSERT OR REPLACE, der in vielen Fällen nützlich sein kann. Wenn Sie jedoch Ihre IDs mit automatischer Inkrementierung aufgrund von Fremdschlüsseln beibehalten möchten, funktioniert dies nicht, da die Zeile gelöscht, eine neue erstellt und folglich diese neue Zeile eine neue ID hat.
Dies wäre die Tabelle:
Spieler - (Primärschlüssel auf ID, Benutzername eindeutig)
| id | user_name | age |
------------------------------
| 1982 | johnny | 23 |
| 1983 | steven | 29 |
| 1984 | pepee | 40 |
db.execSQL("insert into bla(id,name) values (?,?) on conflict(id) do update set name=?")
. Gibt mir einen Syntaxfehler für das Wort "on"Q & A-Stil
Nachdem ich stundenlang nachgeforscht und mit dem Problem gekämpft hatte, stellte ich fest, dass es zwei Möglichkeiten gibt, dies zu erreichen, abhängig von der Struktur Ihrer Tabelle und davon, ob Sie Fremdschlüsseleinschränkungen aktiviert haben, um die Integrität aufrechtzuerhalten. Ich möchte dies in einem sauberen Format teilen, um den Menschen, die sich möglicherweise in meiner Situation befinden, Zeit zu sparen.
Option 1: Sie können es sich leisten, die Zeile zu löschen
Mit anderen Worten, Sie haben keinen Fremdschlüssel, oder wenn Sie ihn haben, ist Ihre SQLite-Engine so konfiguriert, dass es keine Integritätsausnahmen gibt. Der Weg ist EINFÜGEN ODER ERSETZEN . Wenn Sie versuchen, einen Player einzufügen / zu aktualisieren, dessen ID bereits vorhanden ist, löscht die SQLite-Engine diese Zeile und fügt die von Ihnen bereitgestellten Daten ein. Nun stellt sich die Frage: Was tun, um die alte ID in Verbindung zu halten?
Angenommen , wir möchten UPSERT mit den Daten user_name = 'steven' und age = 32 erstellen.
Schauen Sie sich diesen Code an:
Der Trick ist in der Verschmelzung. Es gibt die ID des Benutzers 'steven' zurück, falls vorhanden, und andernfalls wird eine neue neue ID zurückgegeben.
Option 2: Sie können es sich nicht leisten, die Zeile zu löschen
Nachdem ich mit der vorherigen Lösung herumgespielt hatte, wurde mir klar, dass dies in meinem Fall dazu führen könnte, dass Daten zerstört werden, da diese ID als Fremdschlüssel für eine andere Tabelle fungiert. Außerdem habe ich die Tabelle mit der Klausel ON DELETE CASCADE erstellt , was bedeuten würde, dass Daten stillschweigend gelöscht würden. Gefährlich.
Also dachte ich zuerst an eine IF-Klausel, aber SQLite hat nur CASE . Und dieser CASE kann nicht verwendet werden (oder zumindest habe ich ihn nicht verwaltet), um eine UPDATE- Abfrage durchzuführen, wenn EXISTS (ID von Spielern auswählen, bei denen user_name = 'steven'), und INSERT, wenn dies nicht der Fall ist . No Go.
Und dann habe ich endlich die brutale Gewalt mit Erfolg eingesetzt. Die Logik besteht darin, für jedes UPSERT , das Sie ausführen möchten, zuerst ein INSERT ODER IGNORE auszuführen , um sicherzustellen, dass eine Zeile mit unserem Benutzer vorhanden ist, und dann ein UPDATE auszuführen Abfrage mit genau denselben Daten , die Sie einzufügen versucht haben.
Gleiche Daten wie zuvor: Benutzername = 'Steven' und Alter = 32.
Und das ist alles!
BEARBEITEN
Wie Andy kommentiert hat, kann der Versuch, zuerst einzufügen und dann zu aktualisieren, dazu führen, dass Auslöser häufiger als erwartet ausgelöst werden. Dies ist meiner Meinung nach kein Datensicherheitsproblem, aber es ist wahr, dass das Auslösen unnötiger Ereignisse wenig Sinn macht. Daher wäre eine verbesserte Lösung:
quelle
Hier ist ein Ansatz, bei dem das Brute-Force-Ignorieren nicht erforderlich ist. Dies würde nur funktionieren, wenn ein Schlüsselverstoß vorliegt. Dieser Weg funktioniert basierend auf jedem Bedingungen, die Sie im Update angegeben haben.
Versuche dies...
Wie es funktioniert
Die 'magische Sauce' wird hier
Changes()
in derWhere
Klausel verwendet.Changes()
stellt die Anzahl der Zeilen dar, die von der letzten Operation betroffen sind. In diesem Fall handelt es sich um die Aktualisierung.Wenn im obigen Beispiel keine Änderungen gegenüber der Aktualisierung vorgenommen wurden (dh der Datensatz existiert nicht), ist
Changes()
= 0, sodass dieWhere
Klausel in derInsert
Anweisung als wahr ausgewertet wird und eine neue Zeile mit den angegebenen Daten eingefügt wird.Wenn das
Update
tut Update eine vorhandene Zeile, dannChanges()
= 1 (oder genauer gesagt, nicht Null , wenn mehr als eine Zeile aktualisiert wurde), so dass die ‚Wo‘ -Klausel in derInsert
wertet nun falsch und somit wird kein Einsatz stattfinden.Das Schöne daran ist, dass weder Brute-Force erforderlich ist noch unnötiges Löschen und erneutes Einfügen von Daten erforderlich ist, was dazu führen kann, dass nachgeschaltete Schlüssel in Fremdschlüsselbeziehungen durcheinander gebracht werden.
Da es sich nur um eine Standardklausel handelt
Where
, kann sie außerdem auf allem basieren, was Sie definieren, und nicht nur auf Schlüsselverletzungen. Ebenso können SieChanges()
in Kombination mit allem, was Sie wollen / brauchen, überall dort verwenden, wo Ausdrücke erlaubt sind.quelle
Changes() = 0
wird false zurückgegeben und zwei Zeilen werden INSERT OR REPLACE ausführenUPSERT
an? Trotzdem ist es gut, dass das Update, die EinstellungChanges=1
oder dieINSERT
Anweisung fälschlicherweise ausgelöst wird, was Sie nicht möchten.Das Problem mit allen vorgestellten Antworten ist das völlige Fehlen der Berücksichtigung von Auslösern (und wahrscheinlich anderen Nebenwirkungen). Lösung wie
führt dazu, dass beide Trigger ausgeführt werden (zum Einfügen und dann zum Aktualisieren), wenn keine Zeile vorhanden ist.
Die richtige Lösung ist
In diesem Fall wird nur eine Anweisung ausgeführt (wenn eine Zeile vorhanden ist oder nicht).
quelle
UPDATE OR IGNORE
das notwendig ist, da die Aktualisierung nicht abstürzt, wenn keine Zeilen gefunden werden.So haben Sie ein reines UPSERT ohne Löcher (für Programmierer), die keine eindeutigen und anderen Tasten verwenden:
SELECT-Änderungen () geben die Anzahl der Aktualisierungen zurück, die bei der letzten Anfrage durchgeführt wurden. Überprüfen Sie dann, ob der Rückgabewert von change () 0 ist. Führen Sie in diesem Fall Folgendes aus:
quelle
Sie können Ihrer eindeutigen Einschränkung für Benutzername auch einfach eine ON CONFLICT REPLACE-Klausel hinzufügen und dann einfach INSERT entfernen, sodass SQLite überlegt, was im Falle eines Konflikts zu tun ist. Siehe: https://sqlite.org/lang_conflict.html .
Beachten Sie auch den Satz zum Löschen von Triggern: Wenn die Konfliktlösungsstrategie REPLACE Zeilen löscht, um eine Einschränkung zu erfüllen, wird das Löschen von Triggern nur dann ausgelöst, wenn rekursive Trigger aktiviert sind.
quelle
Option 1: Einfügen -> Aktualisieren
Wenn Sie beides vermeiden möchten
changes()=0
undINSERT OR IGNORE
sich das Löschen der Zeile nicht leisten können, können Sie diese Logik verwenden.Zuerst einfügen (falls nicht vorhanden) und dann aktualisieren durch Filtern mit dem eindeutigen Schlüssel .
Beispiel
In Bezug auf Trigger
Hinweis: Ich habe es nicht getestet, um festzustellen, welche Trigger aufgerufen werden, aber ich gehe von Folgendem aus:
wenn Zeile nicht existiert
Wenn eine Zeile vorhanden ist
Option 2: Einfügen oder Ersetzen - behalten Sie Ihre eigene ID
Auf diese Weise können Sie einen einzelnen SQL-Befehl haben
Bearbeiten: Option 2 hinzugefügt.
quelle