Comment mettre à jour une ligne dans une table ou l'INSÉRER si elle n'existe pas?

83

J'ai le tableau de compteurs suivant:

CREATE TABLE cache (
    key text PRIMARY KEY,
    generation int
);

Je voudrais incrémenter l'un des compteurs ou le mettre à zéro si la ligne correspondante n'existe pas encore. Existe-t-il un moyen de le faire sans problèmes de concurrence dans SQL standard? L'opération fait parfois partie d'une transaction, parfois séparée.

Le SQL doit s'exécuter sans modification sur SQLite, PostgreSQL et MySQL, si possible.

Une recherche a donné plusieurs idées qui souffrent de problèmes de concurrence ou sont spécifiques à une base de données:

  • Essayez INSERTune nouvelle ligne et UPDATEs'il y a eu une erreur. Malheureusement, l'erreur sur INSERTabandonne la transaction en cours.

  • UPDATEla ligne, et si aucune ligne n'a été modifiée, INSERTune nouvelle ligne.

  • MySQL a une ON DUPLICATE KEY UPDATEclause.

EDIT: Merci pour toutes les bonnes réponses. Il semble que Paul a raison, et il n'y a pas un seul moyen portable de le faire. C'est assez surprenant pour moi, car cela ressemble à une opération très basique.

Remy Blank
la source
6
Vous n'allez pas trouver une solution unique qui fonctionne pour tous ces SGBDR. Pardon.
Paul Tomblin
1
duplication possible de SQLite - UPSERT * not * INSERT or REPLACE
PearsonArtPhoto
duplication possible des solutions pour INSÉRER OU MISE À JOUR sur SQL Server
Jonathan Leffler

Réponses:

137

MySQL (et ensuite SQLite) prennent également en charge la syntaxe REPLACE INTO:

REPLACE INTO my_table (pk_id, col1) VALUES (5, '123');

Cela identifie automatiquement la clé primaire et trouve une ligne correspondante à mettre à jour, en insérant une nouvelle si aucune n'est trouvée.

Andygeers
la source
13
En fait, pour être précis, REPLACE de MySQL fait toujours une insertion, mais il supprimera d'abord la ligne si elle existe déjà. dev.mysql.com/doc/refman/4.1/en/replace.html
Evan
90
Il est important de comprendre qu'il s'agit d'une insertion + suppression et jamais et mise à jour. La conséquence de ceci est que vous voudrez toujours vous assurer que lorsque vous effectuez un remplacement, vous devez toujours inclure des données pour tous les champs.
Zoredache
2
@Zoredache Techniquement, c'est un «supprimer, puis insérer», car un (n) «insérer + supprimer» est en fait la même chose qu'une suppression mais c'est couper les cheveux.
Agi Hammerthief
2
@Agihammerthief il y a une différence très réelle à savoir que la ligne nouvellement insérée n'aura PAS la même clé primaire que la ligne qui a été supprimée. Avec ON DUPLICATE, ce sera la même clé primaire (sauf si vous la modifiez spécifiquement).
Tim Strijdhorst
32

SQLite prend en charge le remplacement d'une ligne si elle existe déjà:

INSERT OR REPLACE INTO [...blah...]

Vous pouvez raccourcir ceci en

REPLACE INTO [...blah...]

Ce raccourci a été ajouté pour être compatible avec l' REPLACE INTOexpression MySQL .

Kyle Cronin
la source
Cela nécessite que vous définissiez un PRAMARY KEYdans vos valeurs.
DawnSong
24

Je ferais quelque chose comme ce qui suit:

INSERT INTO cache VALUES (key, generation)
ON DUPLICATE KEY UPDATE (key = key, generation = generation + 1);

Définition de la valeur de génération à 0 dans le code ou dans le sql mais en utilisant ON DUP ... pour incrémenter la valeur. Je pense que c'est la syntaxe de toute façon.

jmoz
la source
1
Cette réponse devrait honnêtement être la réponse choisie. C'est une modification non destructive si vous ne soumettez pas tous les champs à une entrée.
Jordanie
9

la clause ON DUPLICATE KEY UPDATE est la meilleure solution car: REPLACE effectue un DELETE suivi d'un INSERT donc pendant une période très courte, l'enregistrement est supprimé, ce qui crée la très faible possibilité qu'une requête puisse revenir après avoir ignoré cela si la page était visualisé lors de la requête REMPLACER.

Je préfère INSÉRER ... SUR LA MISE À JOUR DUPLICATE ... pour cette raison.

La solution de jmoz est la meilleure: bien que je préfère la syntaxe SET aux parenthèses

INSERT INTO cache 
SET key = 'key', generation = 'generation'
ON DUPLICATE KEY 
UPDATE key = 'key', generation = (generation + 1)
;
Corbeau de feu
la source
4
REPLACE est atomique, il n'y a donc pas de période où la ligne n'existe pas.
Brilliand
5

Dans PostgreSQL il n'y a pas de commande de fusion, et en fait l'écrire n'est pas trivial - il y a en fait des cas de bord étranges qui rendent la tâche "intéressante".

La meilleure approche (comme dans: travailler dans les conditions les plus possibles) est d'utiliser une fonction - telle que celle illustrée dans le manuel (merge_db).

Si vous ne souhaitez pas utiliser la fonction, vous pouvez généralement vous en sortir avec:

updated = db.execute(UPDATE ... RETURNING 1)
if (!updated)
  db.execute(INSERT...)

Rappelez - vous qu'il est pas faute de preuve et il va échouer par la suite.


la source
4

Le SQL standard fournit l'instruction MERGE pour cette tâche. Tous les SGBD ne prennent pas en charge l'instruction MERGE.

Jonathan Leffler
la source
0

Si vous n'avez pas de moyen commun de mettre à jour ou d'insérer atomiquement (par exemple, via une transaction), vous pouvez revenir à un autre schéma de verrouillage. Un fichier de 0 octet, un mutex système, un tube nommé, etc ...

Karité
la source
0

Pourriez-vous utiliser un déclencheur d'insertion? En cas d'échec, effectuez une mise à jour.

Michael Todd
la source
Trigger (au moins dans PostgreSQL) est en cours d'exécution lorsque la commande a fonctionné. c'est à dire que vous ne pouvez pas avoir de déclencheur qui s'exécute lorsque la commande de base a échoué.
0

Si vous êtes d'accord avec l'utilisation d'une bibliothèque qui écrit le SQL pour vous, vous pouvez utiliser Upsert (actuellement Ruby et Python uniquement):

Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
Pet.upsert({:name => 'Jerry'}, :color => 'brown')

Cela fonctionne avec MySQL, Postgres et SQLite3.

Il écrit une procédure stockée ou une fonction définie par l'utilisateur (UDF) dans MySQL et Postgres. Il utilise INSERT OR REPLACEdans SQLite3.

Seamus Abshere
la source