SQLite - UPSERT * not * INSERT o REPLACE


535

http://en.wikipedia.org/wiki/Upsert

Inserisci Aggiorna proc memorizzato su SQL Server

Esiste un modo intelligente per farlo in SQLite a cui non ho pensato?

Fondamentalmente voglio aggiornare tre colonne su quattro se il record esiste, se non esiste voglio INSERIRE il record con il valore predefinito (NUL) per la quarta colonna.

L'ID è una chiave primaria, quindi su UPSERT sarà registrato un solo record.

(Sto cercando di evitare il sovraccarico di SELECT per determinare se devo ovviamente AGGIORNARE o INSERIRE)

Suggerimenti?


Non riesco a confermare che la sintassi sul sito SQLite per TABLE CREATE. Non ho creato una demo per testarlo, ma non sembra essere supportato ..

Se lo fosse, ho tre colonne in modo che sembrerebbe effettivamente:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    Blob1 BLOB ON CONFLICT REPLACE, 
    Blob2 BLOB ON CONFLICT REPLACE, 
    Blob3 BLOB 
);

ma i primi due BLOB non causeranno un conflitto, solo l'ID sarebbe quindi Asusme Blob1 e Blob2 non sarebbero sostituiti (come desiderato)


AGGIORNAMENTI in SQLite quando i dati di associazione sono una transazione completa, il che significa che ogni riga inviata per essere aggiornata richiede: Preparare / Associare / Step / Finalizzare le istruzioni a differenza di INSERT che consente l'uso della funzione di ripristino

La vita di un oggetto statement va in questo modo:

  1. Crea l'oggetto usando sqlite3_prepare_v2 ()
  2. Associare i valori ai parametri host utilizzando le interfacce sqlite3_bind_.
  3. Esegui l'SQL chiamando sqlite3_step ()
  4. Reimposta l'istruzione usando sqlite3_reset () quindi torna al passaggio 2 e ripeti.
  5. Distruggi l'oggetto istruzione usando sqlite3_finalize ().

AGGIORNAMENTO Immagino sia lento rispetto a INSERT, ma come si confronta con SELECT usando la chiave primaria?

Forse dovrei usare la selezione per leggere la quarta colonna (Blob3) e quindi usare REPLACE per scrivere un nuovo record mescolando la quarta colonna originale con i nuovi dati per le prime 3 colonne?


5
SQLite - UPSERT disponibile in versione preliminare fare riferimento: sqlite.1065341.n5.nabble.com/…
gaurav

3
UPSERT disponibile nella versione 3.24.0 di SQLite
pablo_worker il

Risposte:


854

Supponendo tre colonne nella tabella: ID, NAME, ROLE


MALE: questo inserirà o sostituirà tutte le colonne con nuovi valori per ID = 1:

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (1, 'John Foo', 'CEO');

BAD: questo inserirà o sostituirà 2 delle colonne ... la colonna NAME verrà impostata su NULL o il valore predefinito:

INSERT OR REPLACE INTO Employee (id, role) 
  VALUES (1, 'code monkey');

BUONO : usa la clausola SQLite On di conflitto SQLERT Supporto UPSERT in SQLite! La sintassi UPSERT è stata aggiunta a SQLite con la versione 3.24.0!

UPSERT è un'aggiunta speciale di sintassi a INSERT che fa sì che INSERT si comporti come AGGIORNAMENTO o no-op se INSERT violerebbe un vincolo di unicità. UPSERT non è SQL standard. UPSERT in SQLite segue la sintassi stabilita da PostgreSQL.

inserisci qui la descrizione dell'immagine

BUONO ma tendo: questo aggiornerà 2 delle colonne. Quando esiste ID = 1, il NAME non sarà interessato. Quando ID = 1 non esiste, il nome sarà il valore predefinito (NULL).

INSERT OR REPLACE INTO Employee (id, role, name) 
  VALUES (  1, 
            'code monkey',
            (SELECT name FROM Employee WHERE id = 1)
          );

Questo aggiornerà 2 delle colonne. Quando esiste ID = 1, il RUOLO non sarà interessato. Quando ID = 1 non esiste, il ruolo verrà impostato su "Benchwarmer" anziché sul valore predefinito.

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );

33
+1 brillante! La clausola di selezione incorporata offre la flessibilità di sovrascrivere la funzionalità ON ON CONFLICT REPLACE predefinita se è necessario combinare / confrontare il vecchio valore e il nuovo valore per qualsiasi campo.
G__

24
Se al Dipendente viene fatto riferimento da altre righe con eliminazione a cascata, le altre righe verranno comunque eliminate in sostituzione.
Don Reba,

246
Questo fa male. SQlite ha bisogno di UPSERT.
Andig

21
Potresti spiegare perché questo inserirà o sostituirà tutte le colonne con nuovi valori per ID = 1: è considerato MALE nel tuo primo esempio? Il comando che presenti lì ha lo scopo di creare un nuovo record con ID 1, nome John Foo e CEO del ruolo , o sovrascrivere il record il cui ID è 1, se è già lì, con quei dati (supponendo che l' id sia la chiave primaria) . Quindi, perché è brutto se succede esattamente?
OR Mapper

10
@Cornelius: è chiaro, ma non è quello che succede nel primo esempio. Il primo esempio ha lo scopo di impostare forzatamente tutte le colonne, che è esattamente ciò che accade, indipendentemente dal fatto che il record sia inserito o sostituito. Quindi, perché è considerato cattivo? La risposta collegata indica anche perché può accadere qualcosa di brutto quando si specifica un sottoinsieme delle colonne, come nel secondo esempio; non sembra elaborare alcun effetto negativo di ciò che accade nel primo esempio, INSERT OR REPLACEspecificando i valori per tutte le colonne.
OR Mapper,

134

INSERIRE O SOSTITUIRE NON equivale a "UPSERT".

Supponiamo di avere la tabella Employee con i campi id, nome e ruolo:

INSERT OR REPLACE INTO Employee ("id", "name", "role") VALUES (1, "John Foo", "CEO")
INSERT OR REPLACE INTO Employee ("id", "role") VALUES (1, "code monkey")

Boom, hai perso il nome del numero di dipendente 1. SQLite lo ha sostituito con un valore predefinito.

Il risultato atteso di un UPSERT sarebbe quello di cambiare il ruolo e mantenere il nome.


21
-1 da me ho paura. Sì, la risposta accettata è sbagliata, ma mentre la tua risposta evidenzia il problema, non è nemmeno una risposta. Per una risposta effettiva, vedere la soluzione intelligente di Eric B che utilizza una coalesce((select ..), 'new value')clausola integrata . Penso che la risposta di Eric abbia bisogno di più voti qui.
Giorno

22
Infatti. Eric è sicuramente la risposta migliore e merita più voti. Detto questo, penso che sottolineando il problema, ho contribuito un po 'a trovare la buona risposta (la risposta di Eric è arrivata più tardi e si basa sulle tabelle sql di esempio nella mia risposta). Quindi non sono sicuro di meritare un -1, ma non
importa

6
+1 per compensare -1 sopra. lol. Cronologia interessante su questo thread però. Chiaramente la tua risposta è stata oltre una settimana prima di Eric, ma entrambi avete risposto alla domanda quasi due anni dopo che è stata posta. +1 anche per Eric, anche se per l'elaborazione.
Rich


2
@QED no, perché delete + insert (che è un sostituto) è composto da 2 istruzioni dml, ad esempio con i propri trigger. Non equivale a 1 sola istruzione di aggiornamento.
Sebas,

109

La risposta di Eric B è OK se si desidera conservare solo una o forse due colonne dalla riga esistente. Se vuoi conservare molte colonne, diventa troppo ingombrante velocemente.

Ecco un approccio che si adatta bene a qualsiasi quantità di colonne su entrambi i lati. Per illustrarlo, assumerò il seguente schema:

 CREATE TABLE page (
     id      INTEGER PRIMARY KEY,
     name    TEXT UNIQUE,
     title   TEXT,
     content TEXT,
     author  INTEGER NOT NULL REFERENCES user (id),
     ts      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );

Nota in particolare che nameè la chiave naturale della riga: idviene utilizzata solo per le chiavi esterne, quindi il punto è che SQLite scelga il valore ID stesso quando si inserisce una nuova riga. Ma quando aggiorno una riga esistente in base alla sua name, voglio che continui ad avere il vecchio valore ID (ovviamente!).

Ottengo un vero UPSERTcon il seguente costrutto:

 WITH new (name, title, author) AS ( VALUES('about', 'About this site', 42) )
 INSERT OR REPLACE INTO page (id, name, title, content, author)
 SELECT old.id, new.name, new.title, old.content, new.author
 FROM new LEFT JOIN page AS old ON new.name = old.name;

La forma esatta di questa query può variare leggermente. La chiave è l'uso diINSERT SELECT con un join esterno sinistro, per unire una riga esistente ai nuovi valori.

Qui, se in precedenza non esisteva una riga, old.idlo sarà NULLe SQLite assegnerà automaticamente un ID, ma se esiste già una riga del genere, old.idavrà un valore effettivo e questo verrà riutilizzato. È esattamente quello che volevo.

In realtà questo è molto flessibile. Nota come la tscolonna sia completamente mancante su tutti i lati - poiché ha un DEFAULTvalore, SQLite farà semplicemente la cosa giusta in ogni caso, quindi non devo occuparmene da solo.

È inoltre possibile includere una colonna su entrambi i newe oldlati e quindi utilizzare ad esempio COALESCE(new.content, old.content)in esterno SELECTper dire “Inserire il nuovo contenuto se c'era, altrimenti mantenere il vecchio contenuto” - ad esempio, se si utilizza una query fisso e sono vincolanti il nuovo valori con segnaposto.


13
+1, funziona alla grande, ma aggiungi un WHERE name = "about"vincolo SELECT ... AS oldper velocizzare le cose. Se hai 1m + righe, questo è molto lento.

Buon punto, +1 sul tuo commento. Lo lascerò fuori dalla risposta, perché l'aggiunta di una WHEREclausola del genere richiede proprio il tipo di ridondanza nella query che stavo cercando di ovviare in primo luogo quando ho trovato questo approccio. Come sempre: quando hai bisogno di prestazioni, denormalizza - la struttura della query, in questo caso.
Aristotele Pagaltzis,

4
Puoi semplificare l'esempio di Aristotele fino a questo, se vuoi: 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' )
jcox,

4
Questo innesco non si innescherebbe ON DELETEquando si esegue una sostituzione (ovvero un aggiornamento)?
Karakuri,

3
Sicuramente attiverà i ON DELETEtrigger. Non so se inutilmente. Per la maggior parte degli utenti, sarebbe probabilmente inutile, anche indesiderato, ma forse non per tutti gli utenti. Allo stesso modo per il fatto che eliminerà anche in cascata tutte le righe con chiavi esterne nella riga in questione, probabilmente un problema per molti utenti. Sfortunatamente, SQLite non ha nulla di più vicino a un vero UPSERT. (Salva per averlo simulato con un INSTEAD OF UPDATEgrilletto, immagino.)
Aristotele Pagaltzis,

86

Se in genere fai aggiornamenti, vorrei ...

  1. Inizia una transazione
  2. Fai l'aggiornamento
  3. Controlla il conteggio delle righe
  4. Se è 0, inserire
  5. Commettere

Se in genere fai inserti, lo farei

  1. Inizia una transazione
  2. Prova un inserto
  3. Verificare l'errore di violazione della chiave primaria
  4. se si è verificato un errore, eseguire l'aggiornamento
  5. Commettere

In questo modo si evita la selezione e si è transazionali sani su Sqlite.


Grazie, ho appena chiesto a questo @ stackoverflow.com/questions/2717590/… . +1 da me! =)
Alix Axel il

4
Se hai intenzione di controllare il conteggio delle righe usando sqlite3_changes () al 3 ° passaggio, assicurati di non utilizzare l'handle DB da più thread per le modifiche.
Linulin,

3
Quanto segue non sarebbe meno prolisso ma con lo stesso effetto: 1) seleziona la tabella del modulo id dove id = 'x' 2) se (ResultSet.rows.length == 0) aggiorna la tabella dove id = 'x';
Florin,

20
Vorrei davvero che INSERT OR UPDATE facesse parte della lingua
Florin,

Questo ha funzionato meglio per me, cercando di fare SOSTITUIRE INTO o sempre e inserire / aggiornare stava uccidendo le mie prestazioni quando ho fatto principalmente aggiornamenti anziché inserti.
Ossido Pherric

83

Questa risposta è stata aggiornata e quindi i commenti qui sotto non si applicano più.

18-05-2018 STOP PRESS.

Supporto UPSERT in SQLite!La sintassi UPSERT è stata aggiunta a SQLite con la versione 3.24.0 (in sospeso)!

UPSERT è un'aggiunta speciale di sintassi a INSERT che fa sì che INSERT si comporti come AGGIORNAMENTO o no-op se INSERT violerebbe un vincolo di unicità. UPSERT non è SQL standard. UPSERT in SQLite segue la sintassi stabilita da PostgreSQL.

inserisci qui la descrizione dell'immagine

in alternativa:

Un altro modo completamente diverso di farlo è: Nella mia applicazione ho impostato il mio ID riga in memoria su long.MaxValue quando creo la riga in memoria. (MaxValue non verrà mai utilizzato come ID e non vivrai abbastanza a lungo .... Quindi se rowID non è quel valore, allora deve già essere nel database, quindi ha bisogno di un AGGIORNAMENTO se è MaxValue, quindi ha bisogno di un inserimento. Ciò è utile solo se è possibile tenere traccia dei rowID nella tua app.


5
Amen. Semplice è meglio di complesso. Questo sembra un po 'più semplice della risposta accettata.
kkurian,

4
Pensavo non potessi fare INSERT INTO ... DOVE in sqlite? Questo è un errore di sintassi per me in sqlite3
Charlie Martin,

5
@CharlieMartin è corretto. Questa sintassi non è valida per SQLite, che è ciò che l'OP ha richiesto. WHERENon è possibile aggiungere una clausola a INSERTun'istruzione: sqlite-insert ...
colm.anseo

4
Questa risposta mi ha fatto perdere un sacco di tempo, la domanda riguarda SQLITE e non sapevo che INSERIRE DOVE non era supportato in sqite, per favore aggiungi questa nota che non è valido per sqlite nella tua risposta
Miquel

3
@colminator e altri: ho corretto la sua istruzione SQL usando il tuo suggerimento.
F Lekschas,

62

Mi rendo conto che questo è un vecchio thread, ma ho lavorato su sqlite3 negli ultimi tempi e ho escogitato questo metodo che si adattava meglio alle mie esigenze di generazione dinamica di query con parametri:

insert or ignore into <table>(<primaryKey>, <column1>, <column2>, ...) values(<primaryKeyValue>, <value1>, <value2>, ...); 
update <table> set <column1>=<value1>, <column2>=<value2>, ... where changes()=0 and <primaryKey>=<primaryKeyValue>; 

Sono ancora 2 query con una clausola where sull'aggiornamento ma sembra fare il trucco. Ho anche questa visione nella mia testa che sqlite può ottimizzare completamente l'istruzione update se la chiamata a changes () è maggiore di zero. Che ciò avvenga o meno al di là delle mie conoscenze, ma un uomo può sognare, no? ;)

Per i punti bonus puoi aggiungere questa riga che ti restituisce l'id della riga sia che si tratti di una riga appena inserita o di una riga esistente.

select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;

+ 1. Esattamente quello che stavo cercando di sostituire facendo una conversione da un codice TSQL di SQL Server. Grazie. SQL che stavo cercando di sostituire era comeUpdate <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
DarrenMB il

14

Ecco una soluzione che in realtà è un UPSERT (UPDATE o INSERT) invece di un INSERT O REPLACE (che funziona in modo diverso in molte situazioni).

Funziona così:
1. Prova ad aggiornare se esiste un record con lo stesso ID.
2. Se l'aggiornamento non ha modificato alcuna riga ( NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)), inserire il record.

Quindi è stato aggiornato un record esistente o verrà eseguito un inserimento.

Il dettaglio importante è utilizzare la funzione change () SQL per verificare se l'istruzione update ha raggiunto qualsiasi record esistente ed eseguire l'istruzione insert solo se non ha raggiunto alcun record.

Una cosa da menzionare è che la funzione change () non restituisce le modifiche eseguite dai trigger di livello inferiore (vedi http://sqlite.org/lang_corefunc.html#changes ), quindi assicurati di tenerne conto.

Ecco l'SQL ...

Aggiornamento di prova:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 2;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 2, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

Inserto di prova:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 3;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 3, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

2
Questa sembra essere la soluzione migliore per me rispetto a quelle di Eric. Tuttavia INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;dovrebbe anche funzionare.
bkausbk,

Grazie amico, funziona anche in WebSQL (usando il plug-in Cordova e SQLite)
Zappescu

11

A partire dalla versione 3.24.0 UPSERT è supportato da SQLite.

Dalla documentazione :

UPSERT è un'aggiunta speciale di sintassi a INSERT che fa sì che INSERT si comporti come AGGIORNAMENTO o no-op se INSERT violerebbe un vincolo di unicità. UPSERT non è SQL standard. UPSERT in SQLite segue la sintassi stabilita da PostgreSQL. La sintassi UPSERT è stata aggiunta a SQLite con la versione 3.24.0 (in sospeso).

Un UPSERT è una normale istruzione INSERT seguita dalla speciale clausola ON CONFLICT

inserisci qui la descrizione dell'immagine

Fonte immagine: https://www.sqlite.org/images/syntax/upsert-clause.gif


8

Puoi davvero fare un upsert in SQLite, sembra solo un po 'diverso da quello a cui sei abituato. Sarebbe simile a:

INSERT INTO table name (column1, column2) 
VALUES ("value12", "value2") WHERE id = 123 
ON CONFLICT DO UPDATE 
SET column1 = "value1", column2 = "value2" WHERE id = 123

5

Espandendo la risposta di Aristotele, puoi SELEZIONARE da una tabella fittizia "singleton" (una tabella di tua creazione con una sola riga). Questo evita alcune duplicazioni.

Ho anche tenuto l'esempio portatile su MySQL e SQLite e ho usato una colonna 'date_added' come esempio di come è possibile impostare una colonna solo la prima volta.

 REPLACE INTO page (
   id,
   name,
   title,
   content,
   author,
   date_added)
 SELECT
   old.id,
   "about",
   "About this site",
   old.content,
   42,
   IFNULL(old.date_added,"21/05/2013")
 FROM singleton
 LEFT JOIN page AS old ON old.name = "about";

4

L'approccio migliore che conosco è fare un aggiornamento, seguito da un inserto. Il "sovraccarico di una selezione" è necessario, ma non è un peso terribile poiché stai cercando la chiave primaria, che è veloce.

Dovresti essere in grado di modificare le seguenti istruzioni con i nomi delle tabelle e dei campi per fare ciò che desideri.

--first, update any matches
UPDATE DESTINATION_TABLE DT
SET
  MY_FIELD1 = (
              SELECT MY_FIELD1
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
 ,MY_FIELD2 = (
              SELECT MY_FIELD2
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
WHERE EXISTS(
            SELECT ST2.PRIMARY_KEY
            FROM
              SOURCE_TABLE ST2
             ,DESTINATION_TABLE DT2
            WHERE ST2.PRIMARY_KEY = DT2.PRIMARY_KEY
            );

--second, insert any non-matches
INSERT INTO DESTINATION_TABLE(
  MY_FIELD1
 ,MY_FIELD2
)
SELECT
  ST.MY_FIELD1
 ,NULL AS MY_FIELD2  --insert NULL into this field
FROM
  SOURCE_TABLE ST
WHERE NOT EXISTS(
                SELECT DT2.PRIMARY_KEY
                FROM DESTINATION_TABLE DT2
                WHERE DT2.PRIMARY_KEY = ST.PRIMARY_KEY
                );

7
Modo complicato.
frast,

Penso che non sia una buona idea perché devi fare due volte una richiesta al motore di database.
Ricardo da Rocha Vitor,

3

Se qualcuno vuole leggere la mia soluzione per SQLite in Cordova, ho ottenuto questo metodo js generico grazie alla risposta @david sopra.

function    addOrUpdateRecords(tableName, values, callback) {
get_columnNames(tableName, function (data) {
    var columnNames = data;
    myDb.transaction(function (transaction) {
        var query_update = "";
        var query_insert = "";
        var update_string = "UPDATE " + tableName + " SET ";
        var insert_string = "INSERT INTO " + tableName + " SELECT ";
        myDb.transaction(function (transaction) {
            // Data from the array [[data1, ... datan],[()],[()]...]:
            $.each(values, function (index1, value1) {
                var sel_str = "";
                var upd_str = "";
                var remoteid = "";
                $.each(value1, function (index2, value2) {
                    if (index2 == 0) remoteid = value2;
                    upd_str = upd_str + columnNames[index2] + "='" + value2 + "', ";
                    sel_str = sel_str + "'" + value2 + "', ";
                });
                sel_str = sel_str.substr(0, sel_str.length - 2);
                sel_str = sel_str + " WHERE NOT EXISTS(SELECT changes() AS change FROM "+tableName+" WHERE change <> 0);";
                upd_str = upd_str.substr(0, upd_str.length - 2);
                upd_str = upd_str + " WHERE remoteid = '" + remoteid + "';";                    
                query_update = update_string + upd_str;
                query_insert = insert_string + sel_str;  
                // Start transaction:
                transaction.executeSql(query_update);
                transaction.executeSql(query_insert);                    
            });
        }, function (error) {
            callback("Error: " + error);
        }, function () {
            callback("Success");
        });
    });
});
}

Quindi, prima prendi i nomi delle colonne con questa funzione:

function get_columnNames(tableName, callback) {
myDb.transaction(function (transaction) {
    var query_exec = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name ='" + tableName + "'";
    transaction.executeSql(query_exec, [], function (tx, results) {
        var columnParts = results.rows.item(0).sql.replace(/^[^\(]+\(([^\)]+)\)/g, '$1').split(','); ///// RegEx
        var columnNames = [];
        for (i in columnParts) {
            if (typeof columnParts[i] === 'string')
                columnNames.push(columnParts[i].split(" ")[0]);
        };
        callback(columnNames);
    });
});
}

Quindi costruire le transazioni a livello di codice.

"Valori" è un array che è necessario creare prima e rappresenta le righe che si desidera inserire o aggiornare nella tabella.

"remoteid" è l'id che ho usato come riferimento, poiché sto sincronizzando con il mio server remoto.

Per l'uso del plug-in Cordova di SQLite, fare riferimento al collegamento ufficiale


2

Questo metodo remixa alcuni degli altri metodi per rispondere a questa domanda e incorpora l'uso di CTE (Common Table Expressions). Presenterò la query e poi spiegherò perché ho fatto quello che ho fatto.

Vorrei cambiare il cognome del dipendente 300 in DAVIS se c'è un dipendente 300. Altrimenti, aggiungerò un nuovo dipendente.

Nome tabella: dipendenti Colonne: id, first_name, last_name

La query è:

INSERT OR REPLACE INTO employees (employee_id, first_name, last_name)
WITH registered_employees AS ( --CTE for checking if the row exists or not
    SELECT --this is needed to ensure that the null row comes second
        *
    FROM (
        SELECT --an existing row
            *
        FROM
            employees
        WHERE
            employee_id = '300'

        UNION

        SELECT --a dummy row if the original cannot be found
            NULL AS employee_id,
            NULL AS first_name,
            NULL AS last_name
    )
    ORDER BY
        employee_id IS NULL --we want nulls to be last
    LIMIT 1 --we only want one row from this statement
)
SELECT --this is where you provide defaults for what you would like to insert
    registered_employees.employee_id, --if this is null the SQLite default will be used
    COALESCE(registered_employees.first_name, 'SALLY'),
    'DAVIS'
FROM
    registered_employees
;

Fondamentalmente, ho usato il CTE per ridurre il numero di volte che l'istruzione select deve essere usata per determinare i valori predefiniti. Poiché si tratta di un CTE, selezioniamo semplicemente le colonne desiderate dalla tabella e l'istruzione INSERT lo utilizza.

Ora puoi decidere quali valori predefiniti vuoi usare sostituendo i null, nella funzione COALESCE con quali dovrebbero essere i valori.


2

A seguito di Aristotele Pagaltzis e l'idea di COALESCEdi risposta di Eric B , qui si tratta di un'opzione upsert per aggiornare solo poche colonne o inserire pieno fila se non esiste.

In questo caso, immagina che il titolo e il contenuto debbano essere aggiornati, mantenendo gli altri vecchi valori quando esistenti e inserendo quelli forniti quando il nome non viene trovato:

NOTA id è costretto a essere NULL quando INSERTsi suppone che sia autoincremento. Se è solo una chiave primaria generata, COALESCEpuò essere utilizzata anche (vedi il commento di Aristotele Pagaltzis ).

WITH new (id, name, title, content, author)
     AS ( VALUES(100, 'about', 'About this site', 'Whatever new content here', 42) )
INSERT OR REPLACE INTO page (id, name, title, content, author)
SELECT
     old.id, COALESCE(old.name, new.name),
     new.title, new.content,
     COALESCE(old.author, new.author)
FROM new LEFT JOIN page AS old ON new.name = old.name;

Quindi la regola generale sarebbe, se vuoi mantenere i vecchi valori, usa COALESCE, quando vuoi aggiornare i valori, usanew.fieldname


COALESCE(old.id, new.id)è decisamente sbagliato con una chiave autoincrementante. E mentre "mantenere invariata la maggior parte della riga, tranne dove mancano i valori" sembra un caso d'uso che qualcuno potrebbe effettivamente avere, non penso che sia quello che le persone cercano quando cercano come fare un UPSERT.
Aristotele Pagaltzis,

@AristotlePagaltzis si scusa se sbaglio, non utilizzo gli autoincrementi. Quello che sto cercando con questa query è aggiornare solo pochi caratteri o inserire una riga intera con i valori forniti nel caso in cui non esista . Ho giocato con la tua query e non sono riuscito a raggiungerlo durante l'inserimento: le colonne selezionate dalla oldtabella dove assegnate NULL, non ai valori forniti new. Questa è la ragione per usare COALESCE. Non sono un esperto di sqlite, sto testando questa query e sembra funzionare per il caso, ti ringrazierei molto se potessi indicarmi la soluzione con autoincrementi
Miquel

2
Nel caso di una chiave autoincrementante, si desidera inserire NULLcome chiave, poiché ciò indica a SQLite di inserire invece il successivo valore disponibile.
Aristotele Pagaltzis,

Non sto dicendo che non dovresti fare quello che stai facendo (se ne hai bisogno, allora ne hai bisogno), solo che non è davvero una risposta alla domanda qui. UPSERT generalmente significa che hai una riga che desideri archiviare nella tabella e semplicemente non sai se hai già una riga corrispondente in cui inserire i valori o se devi inserirli come nuova riga. Il tuo caso d'uso è che se hai già una riga corrispondente, vuoi ignorare la maggior parte dei valori della nuova riga . Va bene, non è solo la domanda che è stata posta.
Aristotele Pagaltzis,

1

Penso che questo potrebbe essere quello che stai cercando: clausola ON CONFLICT .

Se definisci la tua tabella in questo modo:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    field1 TEXT 
); 

Ora, se si esegue un INSERT con un ID già esistente, SQLite esegue automaticamente AGGIORNAMENTO anziché INSERT.

Hth ...


6
Non credo che funzioni, eliminerà le colonne mancanti dall'istruzione insert
Sam Saffron,

3
@Mosor: -1 da parte mia, scusa. Ciò equivale a rilasciare una REPLACEdichiarazione.
Alix Axel,

7
-1 perché ciò comporta un'eliminazione e quindi un inserimento se la chiave primaria esiste già.
frast,

1

Se non ti dispiace farlo in due operazioni.

passi:

1) Aggiungi nuovi elementi con "INSERISCI O IGNORA"

2) Aggiorna gli elementi esistenti con "AGGIORNA"

L'input per entrambi i passaggi è la stessa raccolta di elementi nuovi o che possono essere aggiornati. Funziona bene con oggetti esistenti che non richiedono modifiche. Saranno aggiornati, ma con gli stessi dati e quindi il risultato netto non è cambiato.

Sicuro, più lento, ecc. Inefficiente. Sì.

È facile scrivere sql e mantenerlo e capirlo? Decisamente.

È un compromesso da considerare. Funziona alla grande per piccoli UPS. Funziona alla grande per coloro a cui non dispiace sacrificare l'efficienza per la manutenibilità del codice.


Nota a tutti: INSERIRE O SOSTITUIRE NON è l'argomento di questo post. INSERIRE O SOSTITUIRE creerà una NUOVA riga nella tabella, con un nuovo ID. Questo non è un aggiornamento.
sapbucket,

-2

Dopo aver appena letto questa discussione ed essere rimasto deluso dal fatto che non è stato facile solo per questo "UPSERT", ho studiato ulteriormente ...

Puoi farlo direttamente e facilmente in SQLITE.

Invece di usare: INSERT INTO

Uso: INSERT OR REPLACE INTO

Questo fa esattamente quello che vuoi che faccia!


21
-1 INSERT OR REPLACEnon è un UPSERT. Vedi la "risposta" di gregschlom per il motivo. La soluzione di Eric B in realtà funziona e necessita di alcuni voti.
Giorno

-4
SELECT COUNT(*) FROM table1 WHERE id = 1;

Se COUNT(*) = 0

INSERT INTO table1(col1, col2, cole) VALUES(var1,var2,var3);

altro se COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;

È troppo complicato, SQL può gestirlo bene in una sola query
Robin Kanters il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.