Je dois effectuer UPSERT / INSERT OU UPDATE sur une base de données SQLite.
Il y a la commande INSÉRER OU REMPLACER qui dans de nombreux cas peut être utile. Mais si vous voulez garder vos identifiants avec auto-incrémentation en place à cause de clés étrangères, cela ne fonctionne pas car il supprime la ligne, en crée une nouvelle et par conséquent cette nouvelle ligne a un nouvel identifiant.
Ce serait la table:
joueurs - (clé primaire sur id, nom_utilisateur unique)
| 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=?")
. Me donne une erreur de syntaxe sur le mot "on"Style de questions et réponses
Eh bien, après avoir recherché et combattu le problème pendant des heures, j'ai découvert qu'il y avait deux façons d'accomplir cela, selon la structure de votre table et si vous avez des restrictions de clés étrangères activées pour maintenir l'intégrité. Je voudrais partager ceci dans un format propre pour gagner du temps aux personnes qui peuvent être dans ma situation.
Option 1: vous pouvez vous permettre de supprimer la ligne
En d'autres termes, vous n'avez pas de clé étrangère, ou si vous en avez, votre moteur SQLite est configuré de sorte qu'il n'y ait pas d'exceptions d'intégrité. La voie à suivre est INSÉRER OU REMPLACER . Si vous essayez d'insérer / mettre à jour un lecteur dont l'ID existe déjà, le moteur SQLite supprimera cette ligne et insérera les données que vous fournissez. Maintenant, la question se pose: que faire pour conserver l'ancien ID associé?
Disons que nous voulons UPSERT avec les données user_name = 'steven' et age = 32.
Regardez ce code:
L'astuce est dans la fusion. Il renvoie l'identifiant de l'utilisateur «steven» le cas échéant, et sinon, il renvoie un nouvel identifiant frais.
Option 2: vous ne pouvez pas vous permettre de supprimer la ligne
Après avoir analysé la solution précédente, j'ai réalisé que dans mon cas, cela pourrait finir par détruire des données, car cet ID fonctionne comme une clé étrangère pour une autre table. D'ailleurs, j'ai créé le tableau avec la clause ON DELETE CASCADE , ce qui signifierait qu'elle supprimerait les données en silence. Dangereux.
Donc, j'ai d'abord pensé à une clause IF, mais SQLite n'a que CASE . Et ce CASE ne peut pas être utilisé (ou du moins je ne l'ai pas géré) pour effectuer une requête UPDATE si EXISTS (sélectionnez id parmi les joueurs où user_name = 'steven'), et INSERT si ce n'est pas le cas. Ne pas aller.
Et puis, finalement j'ai utilisé la force brute, avec succès. La logique est que, pour chaque UPSERT que vous souhaitez effectuer, exécutez d'abord un INSERT OU IGNORE pour vous assurer qu'il y a une ligne avec notre utilisateur, puis exécutez une requête UPDATE avec exactement les mêmes données que vous avez essayé d'insérer.
Mêmes données qu'auparavant: user_name = 'steven' et age = 32.
Et c'est tout!
ÉDITER
Comme Andy l'a commenté, essayer d'insérer d'abord, puis de mettre à jour peut conduire à déclencher des déclencheurs plus souvent que prévu. Ce n'est pas à mon avis un problème de sécurité des données, mais il est vrai que déclencher des événements inutiles n'a pas de sens. Par conséquent, une solution améliorée serait:
la source
Voici une approche qui ne nécessite pas la force brute «ignorer» qui ne fonctionnerait qu'en cas de violation de clé. Cette méthode fonctionne en fonction de tout conditions que vous spécifiez dans la mise à jour.
Essaye ça...
Comment ça fonctionne
La «sauce magique» ici est utilisée
Changes()
dans laWhere
clause.Changes()
représente le nombre de lignes affectées par la dernière opération, qui dans ce cas est la mise à jour.Dans l'exemple ci-dessus, s'il n'y a pas de changement depuis la mise à jour (c'est-à-dire que l'enregistrement n'existe pas), alors
Changes()
= 0 donc laWhere
clause de l'Insert
instruction prend la valeur true et une nouvelle ligne est insérée avec les données spécifiées.Si la
Update
a mis à jour une ligne existante, alorsChanges()
= 1 (ou plus précisément, pas zéro si plus d'une ligne a été mise à jour), donc la clause 'Where' dans leInsert
now est évaluée à false et donc aucune insertion n'aura lieu.La beauté de ceci est qu'il n'y a pas de force brute nécessaire, ni de suppression inutilement, puis de réinsertion de données, ce qui peut entraîner la confusion des clés en aval dans les relations de clé étrangère.
De plus, comme il ne s'agit que d'une
Where
clause standard , elle peut être basée sur tout ce que vous définissez, pas seulement sur des violations de clé. De même, vous pouvez utiliserChanges()
en combinaison avec tout ce que vous voulez / avez besoin partout où les expressions sont autorisées.la source
Changes() = 0
, retournera false et deux lignes feront INSERT OR REPLACEUPSERT
en premier lieu? Mais même ainsi, c'est une bonne chose que la mise à jour se produise,Changes=1
sinon laINSERT
déclaration se déclencherait de manière incorrecte, ce que vous ne voulez pas.Le problème avec toutes les réponses présentées est l'absence totale de prise en compte des déclencheurs (et probablement d'autres effets secondaires). Solution comme
conduit à l'exécution des deux déclencheurs (pour l'insertion puis pour la mise à jour) lorsque la ligne n'existe pas.
La bonne solution est
dans ce cas, une seule instruction est exécutée (lorsque la ligne existe ou non).
la source
UPDATE OR IGNORE
est nécessaire, car la mise à jour ne plantera pas si aucune ligne n'est trouvée.Pour avoir un UPSERT pur sans trous (pour les programmeurs) qui ne relaie pas sur des clés uniques et autres:
SELECT changes () renverra le nombre de mises à jour effectuées lors de la dernière demande. Ensuite, vérifiez si la valeur de retour de changes () est 0, si c'est le cas, exécutez:
la source
Vous pouvez également simplement ajouter une clause ON CONFLICT REPLACE à votre contrainte unique user_name, puis simplement INSÉRER, laissant à SQLite le soin de déterminer ce qu'il faut faire en cas de conflit. Voir: https://sqlite.org/lang_conflict.html .
Notez également la phrase concernant les déclencheurs de suppression: lorsque la stratégie de résolution de conflit REPLACE supprime des lignes afin de satisfaire une contrainte, les déclencheurs de suppression se déclenchent si et seulement si les déclencheurs récursifs sont activés.
la source
Option 1: Insérer -> Mettre à jour
Si vous aimez éviter à la fois
changes()=0
etINSERT OR IGNORE
même si vous ne pouvez pas vous permettre de supprimer la ligne - Vous pouvez utiliser cette logique;Tout d'abord, insérez (s'il n'existe pas), puis mettez à jour à en filtrant avec la clé unique.
Exemple
Concernant les déclencheurs
Remarque: je ne l'ai pas testé pour voir quels déclencheurs sont appelés, mais je suppose ce qui suit:
si la ligne n'existe pas
si la ligne existe
Option 2: insérer ou remplacer - conserver votre propre pièce d'identité
de cette façon, vous pouvez avoir une seule commande SQL
Modifier: option ajoutée 2.
la source