http://en.wikipedia.org/wiki/Upsert
Insérer le processus stocké de mise à jour sur SQL Server
Existe-t-il un moyen intelligent de le faire dans SQLite auquel je n'ai pas pensé?
Fondamentalement, je veux mettre à jour trois des quatre colonnes si l'enregistrement existe, s'il n'existe pas, je veux INSÉRER l'enregistrement avec la valeur par défaut (NUL) pour la quatrième colonne.
L'ID est une clé primaire, il n'y aura donc qu'un seul enregistrement sur UPSERT.
(J'essaie d'éviter la surcharge de SELECT afin de déterminer si j'ai besoin de METTRE À JOUR ou INSÉRER évidemment)
Suggestions?
Je ne peux pas confirmer cette syntaxe sur le site SQLite pour TABLE CREATE. Je n'ai pas construit de démo pour le tester, mais il ne semble pas être supporté ..
Si c'était le cas, j'ai trois colonnes, donc cela ressemblerait à:
CREATE TABLE table1(
id INTEGER PRIMARY KEY ON CONFLICT REPLACE,
Blob1 BLOB ON CONFLICT REPLACE,
Blob2 BLOB ON CONFLICT REPLACE,
Blob3 BLOB
);
mais les deux premiers blobs ne causeront pas de conflit, seul l'ID serait donc je asusme Blob1 et Blob2 ne seraient pas remplacés (comme souhaité)
UPDATEs dans SQLite lorsque les données de liaison sont une transaction complète, ce qui signifie que chaque ligne envoyée à mettre à jour nécessite: Préparer / Lier / Étape / Finaliser les instructions contrairement à INSERT qui permet l'utilisation de la fonction de réinitialisation
La vie d'un objet de déclaration ressemble à ceci:
- Créez l'objet à l'aide de sqlite3_prepare_v2 ()
- Liez les valeurs aux paramètres de l'hôte à l'aide des interfaces sqlite3_bind_.
- Exécutez le SQL en appelant sqlite3_step ()
- Réinitialisez l'instruction à l'aide de sqlite3_reset (), puis revenez à l'étape 2 et répétez.
- Détruisez l'objet de déclaration à l'aide de sqlite3_finalize ().
UPDATE Je suppose que c'est lent par rapport à INSERT, mais comment se compare-t-il à SELECT en utilisant la clé primaire?
Peut-être devrais-je utiliser la sélection pour lire la 4e colonne (Blob3) puis utiliser REMPLACER pour écrire un nouvel enregistrement mélangeant la 4e colonne d'origine avec les nouvelles données pour les 3 premières colonnes?
Réponses:
En supposant trois colonnes dans le tableau: ID, NAME, ROLE
MAUVAIS: Cela insérera ou remplacera toutes les colonnes par de nouvelles valeurs pour ID = 1:
MAUVAIS: Cela insérera ou remplacera 2 des colonnes ... la colonne NOM sera définie sur NULL ou la valeur par défaut:
BON : utilisez la prise en charge UPSERT de la clause de conflit SQLite On dans SQLite! La syntaxe UPSERT a été ajoutée à SQLite avec la version 3.24.0!
UPSERT est une addition de syntaxe spéciale à INSERT qui fait que INSERT se comporte comme une MISE À JOUR ou un no-op si INSERT viole une contrainte d'unicité. UPSERT n'est pas du SQL standard. UPSERT dans SQLite suit la syntaxe établie par PostgreSQL.
BON mais tendu: cela mettra à jour 2 des colonnes. Lorsque ID = 1 existe, le NOM n'est pas affecté. Lorsque ID = 1 n'existe pas, le nom sera la valeur par défaut (NULL).
Cela mettra à jour 2 des colonnes. Lorsque ID = 1 existe, le RÔLE ne sera pas affecté. Lorsque ID = 1 n'existe pas, le rôle sera défini sur «Benchwarmer» au lieu de la valeur par défaut.
la source
INSERT OR REPLACE
tout en spécifiant des valeurs pour toutes les colonnes.INSÉRER OU REMPLACER N'EST PAS équivalent à "UPSERT".
Supposons que j'ai la table Employee avec les champs id, name et role:
Boom, vous avez perdu le nom de l'employé numéro 1. SQLite l'a remplacé par une valeur par défaut.
La sortie attendue d'un UPSERT serait de changer le rôle et de conserver le nom.
la source
coalesce((select ..), 'new value')
clause intégrée . Je pense que la réponse d'Eric a besoin de plus de votes.La réponse d'Eric B est OK si vous souhaitez conserver une ou peut-être deux colonnes de la ligne existante. Si vous souhaitez conserver un grand nombre de colonnes, cela devient trop lourd rapidement.
Voici une approche qui s'adapte bien à n'importe quel nombre de colonnes de chaque côté. Pour l'illustrer, je supposerai le schéma suivant:
Notez en particulier que
name
c'est la clé naturelle de la ligne -id
n'est utilisée que pour les clés étrangères, donc le point est pour SQLite de choisir la valeur ID elle-même lors de l'insertion d'une nouvelle ligne. Mais lors de la mise à jour d'une ligne existante en fonction de sonname
, je veux qu'elle continue d'avoir l'ancienne valeur ID (évidemment!).J'atteins un vrai
UPSERT
avec la construction suivante:La forme exacte de cette requête peut varier un peu. La clé est l'utilisation de
INSERT SELECT
avec une jointure externe gauche, pour joindre une ligne existante aux nouvelles valeurs.Ici, si une ligne n'existait pas auparavant, ce
old.id
sera le casNULL
et SQLite attribuera alors un ID automatiquement, mais s'il y avait déjà une telle ligne,old.id
aura une valeur réelle et celle-ci sera réutilisée. C'est exactement ce que je voulais.En fait, c'est très flexible. Notez que la
ts
colonne manque complètement de tous les côtés - car elle a uneDEFAULT
valeur, SQLite fera juste la bonne chose dans tous les cas, donc je n'ai pas à m'en occuper moi-même.Vous pouvez également inclure une colonne sur les deux
new
et lesold
côtés et puis utiliser par exempleCOALESCE(new.content, old.content)
dans la partie extérieureSELECT
de dire « insérer le nouveau contenu s'il y avait, sinon garder l'ancien contenu » - par exemple , si vous utilisez une requête fixe et liez la nouvelle valeurs avec des espaces réservés.la source
WHERE name = "about"
contrainte sur leSELECT ... AS old
pour accélérer les choses. Si vous avez 1m + de lignes, c'est très lent.WHERE
clause nécessite simplement le type de redondance dans la requête que j'essayais d'éviter en premier lieu lorsque j'ai proposé cette approche. Comme toujours: lorsque vous avez besoin de performances, dénormalisez - la structure de la requête, dans ce cas.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
déclencherait-il pas inutilement des déclencheurs lorsqu'il effectue un remplacement (c'est-à-dire une mise à jour)?ON DELETE
déclencheurs. Je ne sais pas inutilement. Pour la plupart des utilisateurs, ce serait probablement inutile, même indésirable, mais peut-être pas pour tous les utilisateurs. De même pour le fait qu'il supprimera également en cascade toutes les lignes avec des clés étrangères dans la ligne en question - probablement un problème pour de nombreux utilisateurs. Malheureusement, SQLite n'a rien de plus proche d'un véritable UPSERT. (Sauf pour le simuler avec unINSTEAD OF UPDATE
déclencheur, je suppose.)Si vous faites généralement des mises à jour, je le ferais ..
Si vous faites généralement des encarts, je
De cette façon, vous évitez la sélection et vous êtes transactionnellement solide sur Sqlite.
la source
Cette réponse a été mise à jour et les commentaires ci-dessous ne s'appliquent donc plus.
2018-05-18 STOP PRESSE.
Prise en charge UPSERT dans SQLite!La syntaxe UPSERT a été ajoutée à SQLite avec la version 3.24.0 (en attente)!
UPSERT est une addition de syntaxe spéciale à INSERT qui fait que INSERT se comporte comme une MISE À JOUR ou un no-op si INSERT viole une contrainte d'unicité. UPSERT n'est pas du SQL standard. UPSERT dans SQLite suit la syntaxe établie par PostgreSQL.
alternativement:
Une autre façon complètement différente de procéder est la suivante: dans mon application, j'ai défini mon en ligne rowID pour qu'il soit long.MaxValue lorsque je crée la ligne en mémoire. (MaxValue ne sera jamais utilisé comme un identifiant, vous ne vivrez pas assez longtemps .... Ensuite, si rowID n'est pas cette valeur, il doit déjà être dans la base de données, il a donc besoin d'une MISE À JOUR s'il s'agit de MaxValue, alors il a besoin d'un insert. Cela n'est utile que si vous pouvez suivre les ID de ligne dans votre application.
la source
WHERE
clause ne peut pas être ajoutée à uneINSERT
instruction: sqlite-insert ...Je me rends compte que c'est un vieux thread mais j'ai travaillé dans sqlite3 récemment et j'ai trouvé cette méthode qui correspondait mieux à mes besoins de génération dynamique de requêtes paramétrées:
C'est toujours 2 requêtes avec une clause where sur la mise à jour mais semble faire l'affaire. J'ai également cette vision en tête que sqlite peut optimiser complètement l'instruction de mise à jour si l'appel à changes () est supérieur à zéro. Qu'il le fasse ou non, cela dépasse mes connaissances, mais un homme peut rêver n'est-ce pas? ;)
Pour les points bonus, vous pouvez ajouter cette ligne qui vous renvoie l'id de la ligne, que ce soit une ligne nouvellement insérée ou une ligne existante.
la source
Update <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
Voici une solution qui est vraiment un UPSERT (UPDATE ou INSERT) au lieu d'un INSERT OR REPLACE (qui fonctionne différemment dans de nombreuses situations).
Cela fonctionne comme ceci:
1. Essayez de mettre à jour si un enregistrement avec le même ID existe.
2. Si la mise à jour n'a modifié aucune ligne (
NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)
), insérez l'enregistrement.Donc, soit un enregistrement existant a été mis à jour, soit une insertion sera effectuée.
Le détail important consiste à utiliser la fonction SQL changes () pour vérifier si l'instruction de mise à jour a atteint des enregistrements existants et à exécuter l'instruction d'insertion uniquement si elle n'a atteint aucun enregistrement.
Une chose à mentionner est que la fonction changes () ne renvoie pas les modifications effectuées par des déclencheurs de niveau inférieur (voir http://sqlite.org/lang_corefunc.html#changes ), alors assurez-vous de prendre cela en compte.
Voici le SQL ...
Mise à jour du test:
Insert de test:
la source
INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;
devrait également fonctionner.À partir de la version 3.24.0, UPSERT est pris en charge par SQLite.
De la documentation :
Source de l'image: https://www.sqlite.org/images/syntax/upsert-clause.gif
la source
Vous pouvez en effet faire un upsert dans SQLite, il semble juste un peu différent de celui auquel vous êtes habitué. Cela ressemblerait à quelque chose comme:
la source
Développer la réponse d'Aristote vous pouvez sélectionner à partir d'une table factice «singleton» (une table de votre propre création avec une seule ligne). Cela évite une certaine duplication.
J'ai également gardé l'exemple portable sur MySQL et SQLite et utilisé une colonne 'date_added' comme exemple de la façon dont vous pouvez définir une colonne uniquement la première fois.
la source
La meilleure approche que je connaisse est de faire une mise à jour, suivie d'une insertion. Le "surcoût d'une sélection" est nécessaire, mais ce n'est pas un fardeau terrible puisque vous recherchez sur la clé primaire, ce qui est rapide.
Vous devriez pouvoir modifier les instructions ci-dessous avec vos noms de table et de champ pour faire ce que vous voulez.
la source
Si quelqu'un veut lire ma solution pour SQLite à Cordova, j'ai obtenu cette méthode générique js grâce à la réponse @david ci-dessus.
Donc, prenez d'abord les noms des colonnes avec cette fonction:
Générez ensuite les transactions par programme.
"Valeurs" est un tableau que vous devez créer avant et il représente les lignes que vous souhaitez insérer ou mettre à jour dans la table.
"remoteid" est l'identifiant que j'ai utilisé comme référence, car je me synchronise avec mon serveur distant.
Pour l'utilisation du plugin SQLite Cordova, veuillez vous référer au lien officiel
la source
Cette méthode remixe quelques-unes des autres méthodes de réponse dans cette question et intègre l'utilisation de CTE (Common Table Expressions). Je vais introduire la requête puis expliquer pourquoi j'ai fait ce que j'ai fait.
Je voudrais changer le nom de famille de l'employé 300 en DAVIS s'il y a un employé 300. Sinon, j'ajouterai un nouvel employé.
Nom de la table: employés Colonnes: id, prénom, nom
La requête est:
Fondamentalement, j'ai utilisé le CTE pour réduire le nombre de fois où l'instruction select doit être utilisée pour déterminer les valeurs par défaut. Puisqu'il s'agit d'un CTE, nous sélectionnons simplement les colonnes que nous voulons dans la table et l'instruction INSERT l'utilise.
Vous pouvez maintenant décider quelles valeurs par défaut vous souhaitez utiliser en remplaçant les valeurs nulles, dans la fonction COALESCE par ce que les valeurs devraient être.
la source
À la suite d' Aristote Pagaltzis et de l'idée de la réponse
COALESCE
d' Eric B , voici une option positive pour mettre à jour seulement quelques colonnes ou insérer une ligne complète si elle n'existe pas.Dans ce cas, imaginez que le titre et le contenu doivent être mis à jour, en conservant les autres anciennes valeurs lors de l'existant et en insérant celles fournies lorsque le nom est introuvable:
NOTE
id
est forcé d'être NULL quandINSERT
il est censé être auto-incrémenté. S'il ne s'agit que d'une clé primaire générée, elleCOALESCE
peut également être utilisée (voir le commentaire d'Aristote Pagaltzis ).Donc, la règle générale serait, si vous souhaitez conserver les anciennes valeurs, utilisez
COALESCE
, lorsque vous souhaitez mettre à jour les valeurs, utiliseznew.fieldname
la source
COALESCE(old.id, new.id)
est définitivement faux avec une clé d'auto-incrémentation. Et bien que «garder la plupart des lignes inchangées, sauf là où des valeurs manquent» ressemble à un cas d'utilisation que quelqu'un pourrait avoir, je ne pense pas que ce soit ce que les gens recherchent lorsqu'ils cherchent à faire un UPSERT.old
table où elles étaient affectéesNULL
, pas aux valeurs fournies dansnew
. C'est la raison d'utiliserCOALESCE
. Je ne suis pas un expert en sqlite, j'ai testé cette requête et semble fonctionner pour le cas, je vous remercie beaucoup si vous pouviez me pointer vers la solution avec autoNULL
comme clé, car cela indique à SQLite d'insérer à la place la prochaine valeur disponible.Je pense que c'est peut-être ce que vous recherchez: clause ON CONFLICT .
Si vous définissez votre table comme ceci:
Maintenant, si vous faites un INSERT avec un identifiant qui existe déjà, SQLite effectue automatiquement la MISE À JOUR au lieu de INSERT.
Hth ...
la source
REPLACE
déclaration.Si cela ne vous dérange pas de le faire en deux opérations.
Pas:
1) Ajoutez de nouveaux éléments avec "INSÉRER OU IGNORER"
2) Mettre à jour les éléments existants avec "UPDATE"
L'entrée des deux étapes est la même collection d'éléments nouveaux ou pouvant être mis à jour. Fonctionne bien avec les éléments existants qui ne nécessitent aucune modification. Ils seront mis à jour, mais avec les mêmes données et donc le résultat net est sans changement.
Bien sûr, plus lent, etc. Inefficace. Oui.
Facile à écrire le sql et à le maintenir et le comprendre? Absolument.
C'est un compromis à considérer. Fonctionne très bien pour les petits upserts. Fonctionne très bien pour ceux qui ne craignent pas de sacrifier l'efficacité pour la maintenabilité du code.
la source
Ayant juste lu ce fil et déçu qu'il n'était pas facile de se contenter de cette ing "UPSERT", j'ai approfondi ...
Vous pouvez réellement le faire directement et facilement dans SQLITE.
À la place d'utiliser:
INSERT INTO
Utilisation:
INSERT OR REPLACE INTO
Cela fait exactement ce que vous voulez qu'il fasse!
la source
INSERT OR REPLACE
n'est pas unUPSERT
. Voir la "réponse" de gregschlom pour la raison. La solution d'Eric B fonctionne et a besoin de quelques votes positifs.si
COUNT(*) = 0
sinon si
COUNT(*) > 0
la source