INSERIRE SE NON ESISTE UN ALTRO AGGIORNAMENTO?


275

Ho trovato alcune "sarebbero" soluzioni per il classico "Come posso inserire un nuovo record o aggiornarne uno se esiste già" ma non riesco a far funzionare nessuno di essi in SQLite.

Ho una tabella definita come segue:

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

Quello che voglio fare è aggiungere un record con un nome univoco. Se il nome esiste già, voglio modificare i campi.

Qualcuno può dirmi come fare per favore?


3
"inserisci o sostituisci" è completamente diverso da "inserisci o aggiorna"
Fattie il

Risposte:


320

Dai un'occhiata a http://sqlite.org/lang_conflict.html .

Vuoi qualcosa come:

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

Si noti che qualsiasi campo non presente nell'elenco di inserimento verrà impostato su NULL se la riga esiste già nella tabella. Questo è il motivo per cui esiste una sottoselezione per la IDcolonna: nel caso di sostituzione l'istruzione lo imposterebbe su NULL e quindi verrebbe assegnato un ID nuovo.

Questo approccio può essere utilizzato anche se si desidera lasciare particolari valori di campo da soli se la riga nel caso di sostituzione ma impostare il campo su NULL nel caso di inserimento.

Ad esempio, supponendo che tu voglia lasciare Seenda solo:

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"));

109
"Inserisci o sostituisci" errato è diverso da "Inserisci o aggiorna". Per una risposta valida, consultare stackoverflow.com/questions/418898/…
rds

12
@rds No, non è sbagliato perché questa domanda dice "modifica i campi" e la chiave primaria non fa parte dell'elenco delle colonne, ma lo sono tutti gli altri campi. Se hai dei casi angolari in cui non stai sostituendo tutti i valori dei campi, o se stai scherzando con la chiave primaria, dovresti fare qualcosa di diverso. Se hai un set completo di nuovi campi, questo approccio è perfetto. Hai un problema specifico che non riesco a vedere?
gennaio

9
È valido se conosci tutti i nuovi valori per tutti i campi. Se solo l'utente aggiorna, ad esempio Level, questo approccio non può essere seguito.
RDS

4
Corretto . L'altra risposta è pertinente, ma questo approccio è valido per l'aggiornamento di tutti i campi (esclusa la chiave).
Ярослав Рахматуллин,

2
Sì Questo è completamente sbagliato. Cosa succederà se voglio solo aggiornare il valore della singola colonna su Confliction da quello nuovo. Nel caso precedente, tutti gli altri dati saranno sostituiti da uno nuovo che non è corretto.
Mrug,

85

È necessario utilizzare il INSERT OR IGNOREcomando seguito da un UPDATEcomando: Nel seguente esempio nameè una chiave primaria:

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

Il primo comando inserirà il record. Se il record esiste, ignorerà l'errore causato dal conflitto con una chiave primaria esistente.

Il secondo comando aggiornerà il record (che ora esiste sicuramente)


6
in quale momento ignorerà? quando il nome e l'età sono entrambi uguali?
mou,

Questa dovrebbe essere la soluzione ... se si utilizza un trigger su insert, la risposta accettata viene attivata ogni volta. Questo non funziona ed esegue solo un aggiornamento
PodTech.io

1
Ignora in base esclusivamente al nome. Ricorda che solo la colonna "nome" è una chiave primaria.
Gabriel Ferrer,

3
Quando il record è nuovo, l'aggiornamento non è necessario ma verrà eseguito comunque, portando a prestazioni scadenti?
MarcG,

Come eseguire una dichiarazione preparata per questo?
prashant

65

È necessario impostare un vincolo sulla tabella per attivare un " conflitto " che si risolve quindi effettuando una sostituzione:

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);

Quindi puoi emettere:

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);

"SELEZIONA * DA dati" ti darà:

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

Si noti che data.id è "3" e non "1" perché REPLACE esegue DELETE e INSERT, non un UPDATE. Ciò significa anche che è necessario assicurarsi di aver definito tutte le colonne necessarie o di ottenere valori NULL imprevisti.


33

Innanzitutto aggiornalo. Se il conteggio delle righe interessate = 0, inserirlo. È il più semplice e adatto a tutti i RDBMS .


11
Due operazioni non dovrebbero essere un problema con una transazione al giusto livello di isolamento, indipendentemente dal database.
Jan

5
Insert or Replaceè davvero più preferibile.
MPelletier,

1
Vorrei davvero che la documentazione IT contenesse più esempi. Ho provato di seguito e non funziona (la mia sintassi è ovviamente sbagliata). Qualche idea su cosa dovrebbe essere? INSERT INTO Book (Nome, TypeID, Level, Visto) VALORI ('Superman', '2', '14', '0') ON CONFLICT REPLACE Book (Nome, TypeID, Livello, Visto) VALUES ('Superman', ' 2 ',' 14 ',' 0 ')
SparkyNZ

6
Inoltre, se i valori delle righe sono esattamente gli stessi, il conteggio delle righe interessate sarà zero e verrà creata una nuova riga duplicata.
barkside,

2
+1, poiché INSERT OR REPLACE eliminerà la riga originale in caso di conflitto e se non si impostano tutte le colonne, si perderanno i valori originali
Pavel Machyniak,

20

INSERT OR REPLACE sostituirà gli altri campi al valore predefinito.

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|||

Se vuoi preservare l'altro campo

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

o usando UPSERT (la sintassi è stata aggiunta a SQLite con la versione 3.24.0 (2018-06-04))

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

Il excluded.prefisso è uguale al valore in VALUES.


16

Upsert è quello che vuoi. UPSERTla sintassi è stata aggiunta a SQLite con la versione 3.24.0 (2018-06-04).

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;

Si noti che a questo punto la parola effettiva "UPSERT" non fa parte della sintassi upsert.

La sintassi corretta è

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

e se stai facendo le INSERT INTO SELECT ...tue esigenze selezionate almeno WHERE trueper risolvere l'ambiguità del parser sul token ONcon la sintassi del join.

Tieni presente che INSERT OR REPLACE...eliminerà il record prima di inserirne uno nuovo se è necessario sostituirlo, il che potrebbe essere negativo se si hanno cascate di chiavi esterne o altri trigger di eliminazione.


Esiste nella documentazione e ho l'ultima versione di sqlite che è la 3.25.1 ma non funziona per me
Bentaiba Miled Basma

hai provato queste due domande sopra alla lettera?
Compagno Joecool il

Si noti che Ubuntu 18.04 viene fornito con SQLite3 v3.22 , non 3.25, quindi non supporta la UPSERTsintassi.
Starbeamrainbowlabs

Grazie per la versione di riferimento sulla funzione!
Matteo

6

Se non si dispone di una chiave primaria, è possibile inserire se non esiste, quindi eseguire un aggiornamento. La tabella deve contenere almeno una voce prima di utilizzare questa.

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';

Questa è l'unica soluzione che ha funzionato per me senza creare voci duplicate. Probabilmente perché la tabella che sto usando non ha chiavi primarie e ha 2 colonne senza valori predefiniti. Anche se è una soluzione un po 'lunga, fa il lavoro correttamente e funziona come previsto.
Inbar Rose,

4

Credo che tu voglia UPSERT .

"INSERISCI O SOSTITUISCI" senza l'inganno aggiuntivo in quella risposta reimposterà tutti i campi che non specifichi su NULL o altri valori predefiniti. (Questo comportamento di INSERT OR REPLACE è diverso da UPDATE; è esattamente come INSERT, perché in realtà è INSERT; tuttavia se ciò che volevi è UPDATE-if-esiste probabilmente vorrai la semantica UPDATE e sarai spiacevolmente sorpreso dal risultato reale.)

Il trucco dell'implementazione UPSERT suggerita è fondamentalmente di utilizzare INSERT OR REPLACE, ma specificare tutti i campi, utilizzando le clausole SELECT incorporate per recuperare il valore corrente per i campi che non si desidera modificare.


1

Penso che valga la pena sottolineare che qui possono esserci comportamenti inaspettati se non capisci bene come interagiscono PRIMARY KEY e UNIQUE .

Ad esempio, se si desidera inserire un record solo se il campo NAME non è attualmente utilizzato e, in tal caso, si desidera che un'eccezione di vincolo venga attivata , quindi INSERT OR REPLACE non genererà ed eccezione e invece verrà risolvere il vincolo UNIQUE stesso sostituendo il record in conflitto (il record esistente con lo stesso NOME ). Gaspard lo dimostra molto bene nella sua risposta sopra.

Se si desidera attivare un'eccezione di vincolo, è necessario utilizzare un'istruzione INSERT e fare affidamento su un comando UPDATE separato per aggiornare il record una volta a conoscenza del nome non assegnato.

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.