http://en.wikipedia.org/wiki/Upsert
Fügen Sie den auf SQL Server gespeicherten Update-Prozess ein
Gibt es eine clevere Möglichkeit, dies in SQLite zu tun, an die ich nicht gedacht habe?
Grundsätzlich möchte ich drei von vier Spalten aktualisieren, wenn der Datensatz vorhanden ist. Wenn er nicht vorhanden ist, möchte ich den Datensatz mit dem Standardwert (NUL) für die vierte Spalte einfügen.
Die ID ist ein Primärschlüssel, sodass UPSERT immer nur einen Datensatz enthält.
(Ich versuche, den Overhead von SELECT zu vermeiden, um festzustellen, ob ich offensichtlich AKTUALISIEREN oder EINFÜGEN muss.)
Vorschläge?
Ich kann diese Syntax auf der SQLite-Site für TABLE CREATE nicht bestätigen. Ich habe keine Demo erstellt, um sie zu testen, aber sie scheint nicht unterstützt zu werden.
Wenn es so wäre, hätte ich drei Spalten, so dass es tatsächlich so aussehen würde:
CREATE TABLE table1(
id INTEGER PRIMARY KEY ON CONFLICT REPLACE,
Blob1 BLOB ON CONFLICT REPLACE,
Blob2 BLOB ON CONFLICT REPLACE,
Blob3 BLOB
);
aber die ersten beiden Blobs verursachen keinen Konflikt, nur die ID würde also Asusme Blob1 und Blob2 würden nicht ersetzt (wie gewünscht)
UPDATEs in SQLite, wenn das Binden von Daten eine vollständige Transaktion ist, was bedeutet, dass jede zu aktualisierende gesendete Zeile Folgendes erfordert: Prepare / Bind / Step / Finalize-Anweisungen im Gegensatz zu INSERT, die die Verwendung der Reset-Funktion ermöglichen
Das Leben eines Anweisungsobjekts sieht ungefähr so aus:
- Erstellen Sie das Objekt mit sqlite3_prepare_v2 ()
- Binden Sie Werte mithilfe von sqlite3_bind_-Schnittstellen an Hostparameter.
- Führen Sie die SQL aus, indem Sie sqlite3_step () aufrufen.
- Setzen Sie die Anweisung mit sqlite3_reset () zurück, kehren Sie dann zu Schritt 2 zurück und wiederholen Sie den Vorgang.
- Zerstören Sie das Anweisungsobjekt mit sqlite3_finalize ().
UPDATE Ich vermute, es ist langsam im Vergleich zu INSERT, aber wie ist es im Vergleich zu SELECT mit dem Primärschlüssel?
Vielleicht sollte ich die Auswahl verwenden, um die 4. Spalte (Blob3) zu lesen, und dann REPLACE verwenden, um einen neuen Datensatz zu schreiben, der die ursprüngliche 4. Spalte mit den neuen Daten für die ersten 3 Spalten mischt?
Antworten:
Angenommen, drei Spalten in der Tabelle: ID, NAME, ROLLE
BAD: Dadurch werden alle Spalten eingefügt oder durch neue Werte für ID = 1 ersetzt:
BAD: Dadurch werden 2 der Spalten eingefügt oder ersetzt. Die NAME-Spalte wird auf NULL oder den Standardwert gesetzt:
GUT : Verwenden Sie die UPSERT-Unterstützung für SQLite On-Konfliktklauseln in SQLite! Die UPSERT-Syntax wurde SQLite mit Version 3.24.0 hinzugefügt!
UPSERT ist eine spezielle Syntaxergänzung zu INSERT, die bewirkt, dass sich INSERT als UPDATE oder No-Op verhält, wenn INSERT eine Eindeutigkeitsbeschränkung verletzen würde. UPSERT ist kein Standard-SQL. UPSERT in SQLite folgt der von PostgreSQL festgelegten Syntax.
GUT, aber nervös: Dadurch werden 2 der Spalten aktualisiert. Wenn ID = 1 vorhanden ist, bleibt der NAME unberührt. Wenn ID = 1 nicht vorhanden ist, ist der Name der Standardname (NULL).
Dadurch werden 2 der Spalten aktualisiert. Wenn ID = 1 vorhanden ist, bleibt die ROLLE unberührt. Wenn ID = 1 nicht vorhanden ist, wird die Rolle anstelle des Standardwerts auf "Benchwarmer" gesetzt.
quelle
INSERT OR REPLACE
einzugehen , während Werte für alle Spalten angegeben werden.INSERT OR REPLACE entspricht NICHT "UPSERT".
Angenommen, ich habe die Tabelle Mitarbeiter mit den Feldern ID, Name und Rolle:
Boom, Sie haben den Namen der Mitarbeiternummer 1 verloren. SQLite hat ihn durch einen Standardwert ersetzt.
Die erwartete Ausgabe eines UPSERT wäre, die Rolle zu ändern und den Namen beizubehalten.
quelle
coalesce((select ..), 'new value')
Klausel. Erics Antwort braucht hier mehr Stimmen, denke ich.Die Antwort von Eric B ist OK, wenn Sie nur eine oder vielleicht zwei Spalten aus der vorhandenen Zeile beibehalten möchten. Wenn Sie viele Spalten beibehalten möchten, wird dies schnell zu umständlich.
Hier ist ein Ansatz, der sich gut auf eine beliebige Anzahl von Spalten auf beiden Seiten skalieren lässt. Zur Veranschaulichung gehe ich von folgendem Schema aus:
Beachten Sie insbesondere, dass dies
name
der natürliche Schlüssel der Zeileid
ist. Er wird nur für Fremdschlüssel verwendet. Daher muss SQLite beim Einfügen einer neuen Zeile den ID-Wert selbst auswählen. Wenn Sie jedoch eine vorhandene Zeile basierend auf ihrer aktualisierenname
, möchte ich, dass sie weiterhin den alten ID-Wert hat (offensichtlich!).Ich erreiche ein wahres
UPSERT
mit dem folgenden Konstrukt:Die genaue Form dieser Abfrage kann etwas variieren. Der Schlüssel ist die Verwendung
INSERT SELECT
mit einem linken äußeren Join, um eine vorhandene Zeile mit den neuen Werten zu verbinden.Wenn eine Zeile zuvor nicht vorhanden war,
old.id
wirdNULL
SQLite automatisch eine ID zuweisen. Wenn jedoch bereits eine solche Zeile vorhanden war,old.id
hat sie einen tatsächlichen Wert und wird wiederverwendet. Welches ist genau das, was ich wollte.In der Tat ist dies sehr flexibel. Beachten Sie, dass die
ts
Spalte auf allen Seiten vollständig fehlt. DaDEFAULT
SQLite einen Wert hat, wird SQLite auf jeden Fall genau das Richtige tun, sodass ich mich nicht selbst darum kümmern muss.Sie können auch eine Spalte auf die beide
new
undold
Seiten und dann zB VerwendungCOALESCE(new.content, old.content)
im AußenSELECT
zu sagen : „ Bitte geben Sie den neuen Inhalt , wenn es eine war, hält sonst den alten Inhalt“ - zB wenn Sie eine feste Abfrage verwenden und unverbindlich die neuen Werte mit Platzhaltern.quelle
WHERE name = "about"
Einschränkung hinzu, um dieSELECT ... AS old
Dinge zu beschleunigen. Wenn Sie mehr als 1 m Zeilen haben, ist dies sehr langsam.WHERE
Klausel nur die Art von Redundanz in der Abfrage erfordert, die ich zu vermeiden versuchte, als ich diesen Ansatz entwickelte. Wie immer: Wenn Sie Leistung benötigen, denormalisieren Sie in diesem Fall die Struktur der Abfrage.INSERT OR REPLACE INTO page (id, name, title, content, author) SELECT id, 'about', 'About this site', content, 42 FROM ( SELECT NULL ) LEFT JOIN ( SELECT * FROM page WHERE name = 'about' )
ON DELETE
wenn ein Ersetzen (dh ein Update) durchgeführt wird?ON DELETE
. Keine Ahnung über unnötig. Für die meisten Benutzer wäre dies wahrscheinlich unnötig, sogar unerwünscht, aber möglicherweise nicht für alle Benutzer. Ebenso für die Tatsache, dass es auch alle Zeilen mit Fremdschlüsseln in die betreffende Zeile kaskadiert - wahrscheinlich ein Problem für viele Benutzer. SQLite hat leider nichts näher an einem echten UPSERT. (Außer, um es mit einemINSTEAD OF UPDATE
AbzugWenn Sie im Allgemeinen Updates durchführen, würde ich ..
Wenn Sie im Allgemeinen Beilagen machen, würde ich
Auf diese Weise vermeiden Sie die Auswahl und sind auf Sqlite transaktionssicher.
quelle
Diese Antwort wurde aktualisiert und daher gelten die folgenden Kommentare nicht mehr.
2018-05-18 STOP PRESSE.
UPSERT-Unterstützung in SQLite! Die UPSERT-Syntax wurde SQLite mit Version 3.24.0 hinzugefügt (ausstehend)!
UPSERT ist eine spezielle Syntaxergänzung zu INSERT, die bewirkt, dass sich INSERT als UPDATE oder No-Op verhält, wenn INSERT eine Eindeutigkeitsbeschränkung verletzen würde. UPSERT ist kein Standard-SQL. UPSERT in SQLite folgt der von PostgreSQL festgelegten Syntax.
Alternative:
Eine andere völlig andere Art, dies zu tun, ist: In meiner Anwendung habe ich meine Zeilen-ID im Speicher auf long festgelegt. MaxValue, wenn ich die Zeile im Speicher erstelle. (MaxValue wird niemals als ID verwendet, Sie werden nicht lange genug leben ... Wenn rowID nicht dieser Wert ist, muss es sich bereits in der Datenbank befinden, benötigt also ein UPDATE, wenn es MaxValue ist, dann braucht es eine Einfügung. Dies ist nur nützlich, wenn Sie die rowIDs in Ihrer App verfolgen können.
quelle
WHERE
Klausel kann nicht an eineINSERT
Anweisung angehängt werden : sqlite-insert ...Mir ist klar, dass dies ein alter Thread ist, aber ich habe in letzter Zeit in sqlite3 gearbeitet und mir diese Methode ausgedacht, die meinen Anforderungen an die dynamische Generierung parametrisierter Abfragen besser entspricht:
Es sind immer noch 2 Abfragen mit einer where-Klausel im Update, aber es scheint den Trick zu tun. Ich habe auch die Vision im Kopf, dass SQLite die Update-Anweisung vollständig optimieren kann, wenn der Aufruf von change () größer als Null ist. Ob dies tatsächlich der Fall ist oder nicht, kann ich nicht wissen, aber ein Mann kann träumen, nicht wahr? ;)
Für Bonuspunkte können Sie diese Zeile anhängen, die Ihnen die ID der Zeile zurückgibt, unabhängig davon, ob es sich um eine neu eingefügte Zeile oder eine vorhandene Zeile handelt.
quelle
Update <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
Hier ist eine Lösung, die wirklich ein UPSERT (UPDATE oder INSERT) anstelle eines INSERT OR REPLACE ist (was in vielen Situationen anders funktioniert).
Dies funktioniert folgendermaßen:
1. Versuchen Sie zu aktualisieren, wenn ein Datensatz mit derselben ID vorhanden ist.
2. Wenn das Update keine Zeilen (
NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)
) geändert hat , fügen Sie den Datensatz ein.Es wurde also entweder ein vorhandener Datensatz aktualisiert oder eine Einfügung durchgeführt.
Das wichtige Detail besteht darin, mit der SQL-Funktion change () zu überprüfen, ob die Update-Anweisung auf vorhandene Datensätze trifft, und die Einfügeanweisung nur auszuführen, wenn sie keinen Datensatz getroffen hat.
Zu erwähnen ist, dass die Funktion "changes ()" keine Änderungen zurückgibt, die von Triggern niedrigerer Ebene ausgeführt wurden (siehe http://sqlite.org/lang_corefunc.html#changes ). Berücksichtigen Sie dies daher unbedingt.
Hier ist die SQL ...
Testupdate:
Testeinsatz:
quelle
INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;
auch funktionieren.Ab Version 3.24.0 wird UPSERT von SQLite unterstützt.
Aus der Dokumentation :
Bildquelle: https://www.sqlite.org/images/syntax/upsert-clause.gif
quelle
Sie können in SQLite tatsächlich ein Upsert durchführen, es sieht nur ein wenig anders aus, als Sie es gewohnt sind. Es würde ungefähr so aussehen:
quelle
Wenn Sie die Antwort von Aristoteles erweitern, können Sie aus einer Dummy-Singleton-Tabelle (einer Tabelle Ihrer eigenen Erstellung mit einer einzelnen Zeile) SELECT auswählen. Dies vermeidet Doppelarbeit.
Ich habe das Beispiel auch für MySQL und SQLite portierbar gehalten und eine 'date_added'-Spalte als Beispiel dafür verwendet, wie Sie eine Spalte nur beim ersten Mal festlegen können.
quelle
Der beste Ansatz, den ich kenne, ist ein Update, gefolgt von einer Einfügung. Der "Overhead einer Auswahl" ist notwendig, aber keine schreckliche Belastung, da Sie nach dem Primärschlüssel suchen, der schnell ist.
Sie sollten in der Lage sein, die folgenden Anweisungen mit Ihren Tabellen- und Feldnamen zu ändern, um das zu tun, was Sie wollen.
quelle
Wenn jemand meine Lösung für SQLite in Cordova lesen möchte, habe ich diese generische js-Methode dank der obigen Antwort von @david erhalten.
Nehmen Sie also zuerst die Spaltennamen mit dieser Funktion auf:
Erstellen Sie dann die Transaktionen programmgesteuert.
"Werte" ist ein Array, das Sie zuvor erstellen sollten und das die Zeilen darstellt, die Sie in die Tabelle einfügen oder aktualisieren möchten.
"remoteid" ist die ID, die ich als Referenz verwendet habe, da ich mit meinem Remote-Server synchronisiere.
Informationen zur Verwendung des SQLite Cordova-Plugins finden Sie unter dem offiziellen Link
quelle
Diese Methode mischt einige der anderen Methoden aus der Antwort auf diese Frage neu und beinhaltet die Verwendung von CTE (Common Table Expressions). Ich werde die Abfrage einführen und dann erklären, warum ich das getan habe, was ich getan habe.
Ich möchte den Nachnamen für Mitarbeiter 300 in DAVIS ändern, wenn es einen Mitarbeiter 300 gibt. Andernfalls füge ich einen neuen Mitarbeiter hinzu.
Tabellenname: Mitarbeiter Spalten: ID, Vorname, Nachname
Die Abfrage lautet:
Grundsätzlich habe ich den CTE verwendet, um die Häufigkeit zu reduzieren, mit der die select-Anweisung zur Ermittlung der Standardwerte verwendet werden muss. Da dies ein CTE ist, wählen wir einfach die gewünschten Spalten aus der Tabelle aus und die INSERT-Anweisung verwendet diese.
Jetzt können Sie entscheiden, welche Standardeinstellungen Sie verwenden möchten, indem Sie die Nullen in der COALESCE-Funktion durch die Werte ersetzen.
quelle
Nach Aristoteles Pagaltzis und der Idee
COALESCE
von Eric B's Antwort ist es hier eine Upsert-Option, nur wenige Spalten zu aktualisieren oder eine vollständige Zeile einzufügen, wenn sie nicht existiert.Stellen Sie sich in diesem Fall vor, dass Titel und Inhalt aktualisiert werden sollten, wobei die anderen alten Werte beibehalten werden, wenn sie vorhanden sind, und die angegebenen Werte eingefügt werden, wenn der Name nicht gefunden wird:
HINWEIS
id
muss NULL sein, wennINSERT
es sich um eine automatische Inkrementierung handelt. Wenn es sich nur um einen generierten Primärschlüssel handelt,COALESCE
kann dieser auch verwendet werden (siehe Kommentar von Aristoteles Pagaltzis ).Die allgemeine Regel wäre also, wenn Sie alte Werte
COALESCE
beibehalten möchten , verwenden Sie , wenn Sie Werte aktualisieren möchten, verwenden Sienew.fieldname
quelle
COALESCE(old.id, new.id)
ist definitiv falsch mit einem automatisch inkrementierenden Schlüssel. Und während "den größten Teil der Zeile unverändert lassen, außer wenn Werte fehlen" wie ein Anwendungsfall klingt, den jemand tatsächlich haben könnte, denke ich nicht, dass die Leute danach suchen, wenn sie nach einer UPSERT-Methode suchen.old
Tabelle ausgewählten Spalten wurden zugewiesenNULL
, nicht den angegebenen Wertennew
. Dies ist der Grund zu verwendenCOALESCE
. Ich bin kein Experte für SQLite, ich habe diese Abfrage getestet und scheint für den Fall zu funktionieren. Ich würde Ihnen vielmals danken, wenn Sie mich auf die Lösung mit automatischen Inkrementen hinweisen könntenNULL
als Schlüssel einfügen , da dies SQLite anweist, stattdessen den nächsten verfügbaren Wert einzufügen.Ich denke, dies könnte das sein, wonach Sie suchen: ON CONFLICT-Klausel .
Wenn Sie Ihre Tabelle folgendermaßen definieren:
Wenn Sie jetzt ein INSERT mit einer bereits vorhandenen ID ausführen, führt SQLite automatisch UPDATE anstelle von INSERT durch.
Hth ...
quelle
REPLACE
Erklärung.Wenn es Ihnen nichts ausmacht, dies in zwei Operationen zu tun.
Schritte:
1) Fügen Sie neue Elemente mit "EINFÜGEN ODER IGNORIEREN" hinzu
2) Aktualisieren Sie vorhandene Elemente mit "UPDATE"
Die Eingabe für beide Schritte ist dieselbe Sammlung neuer oder aktualisierbarer Elemente. Funktioniert gut mit vorhandenen Elementen, die keine Änderungen benötigen. Sie werden aktualisiert, jedoch mit denselben Daten, und daher ändert sich das Nettoergebnis nicht.
Sicher, langsamer usw. Ineffizient. Ja.
Einfach die SQL zu schreiben und zu pflegen und zu verstehen? Bestimmt.
Es ist ein Kompromiss. Funktioniert hervorragend für kleine Aufsätze. Funktioniert hervorragend für diejenigen, denen es nichts ausmacht, die Effizienz für die Wartbarkeit des Codes zu beeinträchtigen.
quelle
Nachdem ich gerade diesen Thread gelesen hatte und enttäuscht war, dass es nicht einfach war, nur zu diesem "UPSERT" zu gehen, untersuchte ich weiter ...
Sie können dies direkt und einfach in SQLITE tun.
Anstatt zu verwenden:
INSERT INTO
Verwenden:
INSERT OR REPLACE INTO
Dies macht genau das, was Sie wollen!
quelle
INSERT OR REPLACE
ist keinUPSERT
. Siehe Gregschloms "Antwort" für den Grund warum. Die Lösung von Eric B funktioniert tatsächlich und benötigt einige positive Stimmen.wenn
COUNT(*) = 0
sonst wenn
COUNT(*) > 0
quelle