web-dev-qa-db-fra.com

Comment puis-je mettre à jour une ligne dans une table ou l'insérer si elle n'existe pas?

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 le SQL standard? L'opération fait parfois partie d'une transaction, parfois distincte.

Le SQL doit être exécuté sans modification sur SQLite, PostgreSQL et MySQL, si possible.

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

  • Essayez de INSERT une nouvelle ligne et UPDATE en cas d'erreur. Malheureusement, l'erreur sur INSERT interrompt la transaction en cours.

  • UPDATE la ligne, et si aucune ligne n'a été modifiée, INSERT une nouvelle ligne.

  • MySQL a un ON DUPLICATE KEY UPDATE clause.

EDIT: Merci pour toutes les bonnes réponses. Il semble que Paul ait 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.

80
Remy Blank

MySQL (et ultérieurement SQLite) supporte également la syntaxe REPLACE INTO:

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

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

133
andygeers

SQLite supporte remplace une ligne si elle existe déjà:

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

Vous pouvez raccourcir ceci pour

REPLACE INTO [...blah...]

Ce raccourci a été ajouté pour être compatible avec MySQL REPLACE INTO expression.

31
Kyle Cronin

Je voudrais faire quelque chose comme ce qui suit:

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

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

20
jmoz

la clause ON DUPLICATE KEY UPDATE est la meilleure solution pour les raisons suivantes: REPLACE effectue un DELETE suivi par un INSERT. visualisé pendant la requête REPLACE.

Je préfère INSERT ... ON DUPLICATE UPDATE ... 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)
;
9
Fire Crow

Je ne sais pas si vous allez trouver une solution indépendante de la plate-forme.

Ceci est communément appelé "UPSERT".

Voir quelques discussions connexes:

8
BradC

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

La meilleure approche (comme dans: travailler dans le plus grand nombre de conditions possible), consiste à utiliser une fonction - telle que celle présentée dans manuel (merge_db).

Si vous ne voulez pas utiliser la fonction, vous pouvez généralement vous échapper avec:

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

Rappelez-vous simplement que ce n’est pas une preuve de faute et que finira par échouer .

5
user80168

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

4
Jonathan Leffler

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

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 REPLACE dans SQLite3.

0
Seamus Abshere

Si vous ne disposez pas d'un moyen commun de mettre à jour ou d'insérer de manière atomique (par exemple, via une transaction), vous pouvez alors recourir à un autre schéma de verrouillage. Un fichier de 0 octet, un mutex système, un canal nommé, etc ...

0
Arnshea

Pourriez-vous utiliser un déclencheur d'insertion? Si cela échoue, effectuez une mise à jour.

0
Michael Todd