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.
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.
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.
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.
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)
;
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:
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 .
Le SQL standard fournit l'instruction MERGE pour cette tâche. Tous les SGBD ne prennent pas en charge l'instruction MERGE.
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.
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 ...
Pourriez-vous utiliser un déclencheur d'insertion? Si cela échoue, effectuez une mise à jour.