INSÉRER S'IL N'EXISTE PAS D'AUTRES MISES À JOUR?

275

J'ai trouvé quelques solutions «qui seraient» pour le classique «Comment insérer un nouvel enregistrement ou en mettre à jour un s'il existe déjà», mais je ne parviens pas à faire fonctionner l'une d'elles dans SQLite.

J'ai un tableau défini comme suit:

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

Ce que je veux faire, c'est ajouter un enregistrement avec un nom unique. Si le nom existe déjà, je souhaite modifier les champs.

Quelqu'un peut-il me dire comment procéder s'il vous plaît?

SparkyNZ
la source
3
"insérer ou remplacer" est totalement différent de "insérer ou mettre à jour"
Fattie

Réponses:

320

Jetez un œil à http://sqlite.org/lang_conflict.html .

Vous voulez quelque chose comme:

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

Notez que tout champ ne figurant pas dans la liste d'insertion sera défini sur NULL si la ligne existe déjà dans la table. C'est pourquoi il y a une sous-sélection pour la IDcolonne: dans le cas de remplacement, l'instruction la mettrait à NULL et ensuite un nouvel ID serait alloué.

Cette approche peut également être utilisée si vous souhaitez laisser des valeurs de champ particulières seules si la ligne dans le cas de remplacement, mais définissez le champ sur NULL dans le cas d'insertion.

Par exemple, en supposant que vous vouliez partir Seenseul:

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
la source
109
Mauvais "insérer ou remplacer" est différent de "insérer ou mettre à jour". Pour une réponse valide, voir stackoverflow.com/questions/418898/…
rds
12
@rds Non, ce n'est pas faux car cette question dit "modifier les champs" et la clé primaire ne fait pas partie de la liste des colonnes, mais tous les autres champs le sont. Si vous allez avoir des cas d'angle où vous ne remplacez pas toutes les valeurs de champ, ou si vous jouez avec la clé primaire, vous devriez faire quelque chose de différent. Si vous avez un ensemble complet de nouveaux domaines, cette approche est très bien. Avez-vous un problème spécifique que je ne vois pas?
janvier 2012
9
C'est valable si vous connaissez toutes les nouvelles valeurs pour tous les champs. Si l'utilisateur ne met à jour que, disons, la Level, cette approche ne peut pas être suivie.
rds
4
Exact . L'autre réponse est pertinente, mais cette approche est valable pour la mise à jour de tous les champs (à l'exclusion de la clé).
Ярослав Рахматуллин
2
Oui, c'est complètement faux. Que se passera-t-il si je souhaite simplement mettre à jour la valeur d'une seule colonne sur Confliction à partir d'une nouvelle. Dans le cas ci-dessus, toutes les autres données seront remplacées par de nouvelles qui ne sont pas correctes.
Mrug
85

Vous devez utiliser la INSERT OR IGNOREcommande suivie d'une UPDATEcommande: dans l'exemple suivant se nametrouve une clé primaire:

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

La première commande insérera l'enregistrement. Si l'enregistrement existe, il ignorera l'erreur provoquée par le conflit avec une clé primaire existante.

La deuxième commande mettra à jour l'enregistrement (qui existe désormais définitivement)

moshik
la source
6
à quel moment il l'ignorera? quand le nom et l'âge sont les mêmes?
mou
Cela devrait être la solution ... si vous utilisez un déclencheur sur l'insert, la réponse acceptée se déclenche à chaque fois. Cela ne fait pas et effectue une mise à jour uniquement
PodTech.io
1
Il ignore basé uniquement sur le nom. N'oubliez pas que seule la colonne "nom" est une clé primaire.
Gabriel Ferrer
3
Lorsque l'enregistrement est nouveau, la mise à jour n'est pas nécessaire mais sera exécutée de toute façon, entraînant de mauvaises performances?
MarcG
Comment exécuter une instruction préparée pour cela?
prashant le
65

Vous devez définir une contrainte sur la table pour déclencher un " conflit " que vous résolvez ensuite en effectuant un remplacement:

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

Ensuite, vous pouvez émettre:

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

Le "SELECT * FROM data" vous donnera:

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

Notez que le data.id est "3" et non "1" car REPLACE effectue un DELETE et INSERT, pas un UPDATE. Cela signifie également que vous devez vous assurer que vous définissez toutes les colonnes nécessaires ou vous obtiendrez des valeurs NULL inattendues.

gaspard
la source
33

Tout d'abord, mettez-le à jour. Si le nombre de lignes affectées = 0, insérez-le. C'est le plus simple et adapté à tous les SGBDR .

Burçin
la source
11
Deux opérations ne devraient poser aucun problème avec une transaction au bon niveau d'isolement, quelle que soit la base de données.
2010
5
Insert or Replaceest vraiment plus préférable.
MPelletier
1
Je souhaite vraiment que la documentation informatique contienne plus d'exemples. J'ai essayé ceci ci-dessous et cela ne fonctionne pas (ma syntaxe est évidemment fausse). Des idées sur ce que ça devrait être? 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
Et si les valeurs de ligne sont exactement les mêmes, le nombre de lignes affectées sera nul et une nouvelle ligne en double sera créée.
2012
2
+1, car INSÉRER OU REMPLACER supprimera la ligne d'origine en cas de conflit et si vous ne définissez pas toutes les colonnes, vous perdrez les valeurs d'origine
Pavel Machyniak
20

INSERT OR REPLACE remplacera les autres champs à la valeur par défaut.

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

Si vous souhaitez conserver l'autre champ

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

ou en utilisant UPSERT (la syntaxe a été ajoutée à SQLite avec la version 3.24.0 (2018-06-04))

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

Le excluded.préfixe égal à la valeur en VALUES.

Aile d'acier
la source
16

Upsert est ce que vous voulez. UPSERTla syntaxe a été ajoutée à SQLite avec la version 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;

Soyez averti qu'à ce stade, le mot "UPSERT" ne fait pas partie de la syntaxe upsert.

La syntaxe correcte est

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

et si vous effectuez INSERT INTO SELECT ...votre sélection, au moins WHERE truepour résoudre l'ambiguïté de l'analyseur sur le jeton ONavec la syntaxe de jointure.

Soyez averti que INSERT OR REPLACE...supprimera l'enregistrement avant d'en insérer un nouveau s'il doit être remplacé, ce qui pourrait être mauvais si vous avez des cascades de clés étrangères ou d'autres déclencheurs de suppression.

Camarade Joecool
la source
Il existe dans la documentation et j'ai la dernière version de sqlite qui est 3.25.1 mais cela ne fonctionne pas pour moi
Bentaiba Miled Basma
avez-vous essayé ces deux requêtes ci-dessus textuellement?
ComradeJoecool
Notez que Ubuntu 18.04 est livré avec SQLite3 v3.22 , et non 3.25, donc il ne prend pas en charge la UPSERTsyntaxe.
starbeamrainbowlabs
Merci pour la version de référence sur la fonctionnalité!
Matteo
6

Si vous n'avez pas de clé primaire, vous pouvez l'insérer si elle n'existe pas, puis faire une mise à jour. Le tableau doit contenir au moins une entrée avant de l'utiliser.

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';
mat
la source
C'est la seule solution qui a fonctionné pour moi sans créer d'entrées en double. Probablement parce que la table que j'utilise n'a pas de clés primaires et a 2 colonnes sans valeurs par défaut. Même si c'est une solution un peu longue, elle fait le travail correctement et fonctionne comme prévu.
Inbar Rose
4

Je crois que vous voulez UPSERT .

"INSÉRER OU REMPLACER" sans la supercherie supplémentaire dans cette réponse réinitialise tous les champs que vous ne spécifiez pas à NULL ou à une autre valeur par défaut. (Ce comportement de INSERT OR REPLACE est différent de UPDATE; c'est exactement comme INSERT, car il s'agit en fait de INSERT; cependant si ce que vous vouliez est UPDATE-si-existe, vous voulez probablement la sémantique UPDATE et serez désagréablement surpris par le résultat réel.)

La ruse de l'implémentation UPSERT suggérée consiste essentiellement à utiliser INSERT OR REPLACE, mais spécifiez tous les champs, en utilisant des clauses SELECT intégrées pour récupérer la valeur actuelle des champs que vous ne souhaitez pas modifier.

metamatt
la source
1

Je pense qu'il vaut la peine de souligner qu'il peut y avoir un comportement inattendu ici si vous ne comprenez pas complètement comment PRIMARY KEY et UNIQUE interagissent.

Par exemple, si vous souhaitez insérer un enregistrement uniquement si le champ NOM n'est pas actuellement pris, et si c'est le cas, vous voulez qu'une exception de contrainte se déclenche pour vous le dire, alors INSERT OR REPLACE ne lancera pas d'exception et au lieu de cela résoudre la contrainte UNIQUE elle-même en remplaçant l'enregistrement en conflit (l'enregistrement existant avec le même NOM ). Gaspard le démontre très bien dans sa réponse ci-dessus.

Si vous souhaitez qu'une exception de contrainte se déclenche, vous devez utiliser une instruction INSERT et compter sur une commande UPDATE distincte pour mettre à jour l'enregistrement une fois que vous savez que le nom n'est pas pris.

Matt Matthias
la source