Il y a plusieurs mois, j'ai appris d'une réponse sur Stack Overflow comment effectuer plusieurs mises à jour à la fois dans MySQL en utilisant la syntaxe suivante:
INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);
Je suis maintenant passé à PostgreSQL et apparemment, ce n'est pas correct. Cela fait référence à toutes les tables correctes, donc je suppose que c'est une question de mots-clés différents utilisés, mais je ne sais pas où dans la documentation PostgreSQL cela est couvert.
Pour clarifier, je veux insérer plusieurs choses et si elles existent déjà pour les mettre à jour.
sql
postgresql
upsert
sql-merge
Teifion
la source
la source
Réponses:
PostgreSQL depuis la version 9.5 a la syntaxe UPSERT , avec la clause ON CONFLICT . avec la syntaxe suivante (similaire à MySQL)
La recherche dans les archives du groupe de messagerie de postgresql de "upsert" conduit à trouver un exemple de ce que vous voulez faire, dans le manuel :
Il y a peut-être un exemple de la façon de le faire en masse, en utilisant les CTE dans 9.1 et au-dessus, dans la liste de diffusion des pirates :
Voir la réponse de a_horse_with_no_name pour un exemple plus clair.
la source
excluded
référence la première solution ici?excluded
tableau spécial vous donne accès aux valeurs que vous tentiez d'insérer en premier lieu.Avertissement: ce n'est pas sûr s'il est exécuté à partir de plusieurs sessions en même temps (voir les mises en garde ci-dessous).
Une autre façon intelligente de faire un "UPSERT" dans postgresql est de faire deux instructions UPDATE / INSERT séquentielles conçues chacune pour réussir ou pour n'avoir aucun effet.
La MISE À JOUR réussira si une ligne avec "id = 3" existe déjà, sinon elle n'a aucun effet.
L'INSERT ne réussira que si la ligne avec "id = 3" n'existe pas déjà.
Vous pouvez combiner ces deux éléments en une seule chaîne et les exécuter tous les deux avec une seule instruction SQL exécutée à partir de votre application. Les exécuter ensemble en une seule transaction est fortement recommandé.
Cela fonctionne très bien lorsqu'il est exécuté de manière isolée ou sur une table verrouillée, mais est soumis à des conditions de concurrence qui signifie qu'il peut toujours échouer avec une erreur de clé en double si une ligne est insérée simultanément, ou peut se terminer sans aucune ligne insérée lorsqu'une ligne est supprimée simultanément . Une
SERIALIZABLE
transaction sur PostgreSQL 9.1 ou supérieur la traitera de manière fiable au prix d'un taux d'échec de sérialisation très élevé, ce qui signifie que vous devrez réessayer beaucoup. Voyez pourquoi est upsert si compliqué , qui traite de ce cas plus en détail.Cette approche est également sujette à des mises à jour perdues
read committed
isolément, à moins que l'application ne vérifie le nombre de lignes affectées et ne vérifie que lainsert
ou la ligneupdate
affectée .la source
... where not exists (select 1 from table where id = 3);
read committed
isolées, sauf si votre application vérifie que leinsert
ou le nombre deupdate
lignes est différent de zéro. Voir dba.stackexchange.com/q/78510/7788Avec PostgreSQL 9.1 cela peut être réalisé en utilisant un CTE inscriptible ( expression de table commune ):
Voir ces entrées de blog:
Notez que cette solution n'empêche pas une violation de clé unique mais qu'elle n'est pas vulnérable aux mises à jour perdues.
Voir le suivi de Craig Ringer sur dba.stackexchange.com
la source
UPDATE
lignes concernées soient affectées.Dans PostgreSQL 9.5 et plus récent, vous pouvez utiliser
INSERT ... ON CONFLICT UPDATE
.Consultez la documentation .
Un MySQL
INSERT ... ON DUPLICATE KEY UPDATE
peut être directement reformulé en aON CONFLICT UPDATE
. La syntaxe standard SQL n'est pas non plus, ce sont deux extensions spécifiques à la base de données. Il y a de bonnes raisons pour lesquelles celaMERGE
n'a pas été utilisé , une nouvelle syntaxe n'a pas été créée juste pour le plaisir. (La syntaxe de MySQL a également des problèmes qui signifient qu'elle n'a pas été adoptée directement).par exemple, configuration donnée:
la requête MySQL:
devient:
Différences:
Vous devez spécifier le nom de colonne (ou le nom de contrainte unique) à utiliser pour la vérification d'unicité. C'est le
ON CONFLICT (columnname) DO
Le mot-clé
SET
doit être utilisé, comme s'il s'agissait d'uneUPDATE
instruction normaleIl a aussi de belles fonctionnalités:
Vous pouvez avoir une
WHERE
clause sur votreUPDATE
(vous permettant de transformer efficacementON CONFLICT UPDATE
enON CONFLICT IGNORE
certaines valeurs)Les valeurs proposées pour l'insertion sont disponibles en tant que variable de ligne
EXCLUDED
, qui a la même structure que la table cible. Vous pouvez obtenir les valeurs d'origine dans la table en utilisant le nom de la table. Donc, dans ce cas, ceEXCLUDED.c
sera10
(parce que c'est ce que nous avons essayé d'insérer) et ce"table".c
sera3
parce que c'est la valeur actuelle dans le tableau. Vous pouvez utiliser l'un ou les deux dans lesSET
expressions et laWHERE
clause.Pour des informations sur upsert, voir Comment UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) dans PostgreSQL?
la source
ON DUPLICATE KEY UPDATE
. J'ai téléchargé Postgres 9.5 et implémenté votre code mais étrangement le même problème se produit sous Postgres: le champ série de la clé primaire n'est pas consécutif (il y a des écarts entre les insertions et les mises à jour.). Une idée de ce qui se passe ici? Est-ce normal? Une idée comment éviter ce comportement? Je vous remercie.SERIAL
/SEQUENCE
ouAUTO_INCREMENT
ne pas avoir de lacunes. Si vous avez besoin de séquences sans espace, elles sont plus complexes; vous devez généralement utiliser une table de comptoir. Google vous en dira plus. Mais sachez que les séquences sans espace empêchent toutes les insertions simultanées.BEGIN ... EXCEPTION ...
exécutions dans une sous-transaction qui est annulée en cas d'erreur, votre incrément de séquence serait annulé en cas d'INSERT
échec.Je cherchais la même chose quand je suis venu ici, mais le manque d'une fonction générique "upsert" m'a un peu dérangé alors j'ai pensé que vous pouviez simplement passer la mise à jour et insérer sql comme arguments sur cette fonction du manuel
cela ressemblerait à ceci:
et peut-être pour faire ce que vous vouliez initialement faire, batch "upsert", vous pouvez utiliser Tcl pour diviser sql_update et boucler les mises à jour individuelles, le hit de pré-performance sera très petit voir http://archives.postgresql.org/pgsql- performance / 2006-04 / msg00557.php
le coût le plus élevé est l'exécution de la requête à partir de votre code, du côté de la base de données, le coût d'exécution est beaucoup plus faible
la source
DELETE
moins que vous ne verrouilliez la table ou que vous soyez enSERIALIZABLE
isolation de transaction sur PostgreSQL 9.1 ou supérieur.Il n'y a pas de commande simple pour le faire.
L'approche la plus correcte consiste à utiliser la fonction, comme celle des documents .
Une autre solution (bien que moins sûre) consiste à effectuer une mise à jour avec retour, à vérifier quelles lignes étaient des mises à jour et à insérer les autres
Quelque chose dans le sens de:
en supposant que id: 2 a été renvoyé:
Bien sûr, il renflouera tôt ou tard (dans un environnement simultané), car il y a une condition de course claire ici, mais généralement cela fonctionnera.
Voici un article plus long et plus complet sur le sujet .
la source
Personnellement, j'ai mis en place une "règle" attachée à l'instruction d'insertion. Supposons que vous disposiez d'une table "DNS" qui enregistre les hits DNS par client sur une base de temps:
Vous vouliez pouvoir réinsérer des lignes avec des valeurs mises à jour ou les créer si elles n'existaient pas déjà. Saisissez l'identifiant client et l'heure. Quelque chose comme ça:
Mise à jour: cela peut échouer si des insertions simultanées se produisent, car cela générera des exceptions de violation unique. Cependant, la transaction non terminée se poursuivra et réussira, et il vous suffit de répéter la transaction terminée.
Cependant, s'il y a des tonnes d'insertions tout le temps, vous souhaiterez mettre un verrou de table autour des instructions d'insertion: le verrouillage SHARE ROW EXCLUSIVE empêchera toutes les opérations qui pourraient insérer, supprimer ou mettre à jour des lignes dans votre table cible. Cependant, les mises à jour qui ne mettent pas à jour la clé unique sont sûres. Par conséquent, si aucune opération ne le fait, utilisez plutôt des verrous consultatifs.
De plus, la commande COPY n'utilise pas de RULES, donc si vous insérez avec COPY, vous devrez utiliser des déclencheurs à la place.
la source
J'utilise cette fonction de fusion
la source
update
première, puis de vérifier le nombre de lignes mises à jour. (Voir la réponse d'Ahmad)J'ai personnalisé la fonction "upsert" ci-dessus, si vous voulez INSÉRER ET REMPLACER:
"
Et après l'exécution, faites quelque chose comme ceci:
Est important de mettre une double virgule pour éviter les erreurs du compilateur
la source
Semblable à la réponse la plus appréciée, mais fonctionne légèrement plus rapidement:
(source: http://www.the-art-of-web.com/sql/upsert/ )
la source
J'ai le même problème de gestion des paramètres de compte que les paires de valeurs de nom. Le critère de conception est que différents clients peuvent avoir des ensembles de paramètres différents.
Ma solution, similaire à JWP, consiste à effacer et remplacer en bloc, générant l'enregistrement de fusion dans votre application.
C'est assez à l'épreuve des balles, indépendant de la plate-forme et comme il n'y a jamais plus de 20 paramètres par client, ce ne sont que 3 appels db à charge assez faible - probablement la méthode la plus rapide.
L'alternative de mettre à jour des lignes individuelles - vérifier les exceptions puis insérer - ou une combinaison de codes hideux, lents et se casse souvent parce que (comme mentionné ci-dessus) la gestion des exceptions SQL non standard passant de db à db - ou même de version en version.
la source
REPLACE INTO
queINSERT INTO ... ON DUPLICATE KEY UPDATE
, ce qui peut provoquer un problème si vous utilisez des déclencheurs. Vous finirez par exécuter la suppression et l'insertion de déclencheurs / règles, plutôt que de mettre à jour ceux.Selon la documentation PostgreSQL de l'
INSERT
instruction , la gestion duON DUPLICATE KEY
cas n'est pas prise en charge. Cette partie de la syntaxe est une extension MySQL propriétaire.la source
MERGE
est aussi vraiment plus une opération OLAP; voir stackoverflow.com/q/17267417/398670 pour des explications. Il ne définit pas la sémantique de concurrence et la plupart des gens qui l'utilisent pour upsert ne font que créer des bugs.la source
Pour fusionner de petits ensembles, l'utilisation de la fonction ci-dessus est très bien. Cependant, si vous fusionnez de grandes quantités de données, je vous suggère de consulter http://mbk.projects.postgresql.org
La meilleure pratique actuelle que je connaisse est:
la source
UPDATE renverra le nombre de lignes modifiées. Si vous utilisez JDBC (Java), vous pouvez alors comparer cette valeur à 0 et, si aucune ligne n'a été affectée, déclencher INSERT à la place. Si vous utilisez un autre langage de programmation, peut-être que le nombre de lignes modifiées peut encore être obtenu, consultez la documentation.
Cela peut ne pas être aussi élégant, mais vous disposez d'un SQL beaucoup plus simple et plus simple à utiliser à partir du code appelant. Différemment, si vous écrivez le script de dix lignes en PL / PSQL, vous devriez probablement avoir un test unitaire de l'un ou l'autre type juste pour lui seul.
la source
Modifier: cela ne fonctionne pas comme prévu. Contrairement à la réponse acceptée, cela produit des violations de clé uniques lorsque deux processus appellent à plusieurs reprises
upsert_foo
simultanément.Eureka! J'ai trouvé un moyen de le faire dans une seule requête: utilisez
UPDATE ... RETURNING
pour tester si des lignes ont été affectées:Le
UPDATE
doit être fait dans une procédure distincte parce que, malheureusement, cela est une erreur de syntaxe:Maintenant, cela fonctionne comme vous le souhaitez:
la source