Une question très fréquemment posée ici est de savoir comment faire un upsert, ce que MySQL appelle INSERT ... ON DUPLICATE UPDATE
et la norme prend en charge dans le cadre de l' MERGE
opération.
Étant donné que PostgreSQL ne le prend pas directement en charge (avant la page 9.5), comment procédez-vous? Considérer ce qui suit:
CREATE TABLE testtable (
id integer PRIMARY KEY,
somedata text NOT NULL
);
INSERT INTO testtable (id, somedata) VALUES
(1, 'fred'),
(2, 'bob');
Maintenant , imaginez que vous voulez « upsert » les tuples (2, 'Joe')
, (3, 'Alan')
, de sorte que le nouveau contenu de la table serait:
(1, 'fred'),
(2, 'Joe'), -- Changed value of existing tuple
(3, 'Alan') -- Added new tuple
C'est de cela que les gens parlent lorsqu'ils discutent d'un upsert
. Surtout, toute approche doit être sûre en présence de plusieurs transactions travaillant sur la même table - soit en utilisant un verrouillage explicite, soit en se défendant contre les conditions de concurrence qui en résultent.
Ce sujet est abordé en détail dans Insert, sur la mise à jour en double dans PostgreSQL? , mais il s'agit d'alternatives à la syntaxe MySQL, et elle a augmenté un peu de détails non liés au fil du temps. Je travaille sur des réponses définitives.
Ces techniques sont également utiles pour "insérer s'il n'existe pas, sinon ne rien faire", c'est-à-dire "insérer ... sur une clé en double ignorer".
la source
Réponses:
9.5 et plus récent:
PostgreSQL 9.5 et support plus récent
INSERT ... ON CONFLICT UPDATE
(etON CONFLICT DO NOTHING
), ie upsert.Comparaison avec
ON DUPLICATE KEY UPDATE
.Explication rapide .
Pour l'utilisation, voir le manuel - en particulier la clause conflict_action dans le diagramme de syntaxe et le texte explicatif .
Contrairement aux solutions pour 9.4 et antérieures présentées ci-dessous, cette fonctionnalité fonctionne avec plusieurs lignes en conflit et ne nécessite pas de verrouillage exclusif ni de boucle de relance.
Le commit ajoutant la fonctionnalité est ici et la discussion autour de son développement est ici .
Si vous êtes sur 9.5 et n'avez pas besoin d'être rétrocompatible, vous pouvez arrêter la lecture maintenant .
9.4 et plus:
PostgreSQL n'a pas de fonction intégrée
UPSERT
(ouMERGE
), et le faire efficacement face à une utilisation simultanée est très difficile.Cet article décrit le problème en détail utile .
En général, vous devez choisir entre deux options:
Boucle de relance de ligne individuelle
L'utilisation de sauts de ligne individuels dans une boucle de nouvelle tentative est l'option raisonnable si vous souhaitez que de nombreuses connexions essaient simultanément d'effectuer des insertions.
La documentation PostgreSQL contient une procédure utile qui vous permettra de le faire en boucle à l'intérieur de la base de données . Il protège contre les mises à jour perdues et les courses d'insertion, contrairement à la plupart des solutions naïves. Cela ne fonctionnera qu'en
READ COMMITTED
mode et n'est sûr que si c'est la seule chose que vous faites dans la transaction. La fonction ne fonctionnera pas correctement si les déclencheurs ou les clés uniques secondaires provoquent des violations uniques.Cette stratégie est très inefficace. Dans la mesure du possible, vous devez mettre le travail en file d'attente et effectuer une mise à jour groupée comme décrit ci-dessous.
De nombreuses tentatives de solutions à ce problème ne prennent pas en compte les annulations, elles entraînent donc des mises à jour incomplètes. Deux transactions se font la course; l'un d'eux a réussi
INSERT
s; l'autre obtient une erreur de clé en double et fait à laUPDATE
place. LesUPDATE
blocs en attenteINSERT
de restauration ou de validation. Lorsqu'elle est annulée, laUPDATE
nouvelle vérification de la condition correspond à zéro ligne, donc même siUPDATE
les validations n'ont pas réellement fait l'upert que vous attendiez. Vous devez vérifier le nombre de lignes de résultats et réessayer si nécessaire.Certaines solutions tentées ne tiennent pas compte non plus des races SELECT. Si vous essayez l'évidence et la simplicité:
puis, lorsque deux fonctionnent en même temps, il existe plusieurs modes de défaillance. L'un est le problème déjà discuté avec une nouvelle vérification de la mise à jour. Un autre est où les deux
UPDATE
en même temps, correspondant à zéro ligne et continuant. Ensuite, ils font tous les deux leEXISTS
test, qui a lieu avant leINSERT
. Les deux obtiennent zéro ligne, donc les deux font leINSERT
. Un échoue avec une erreur de clé en double.C'est pourquoi vous avez besoin d'une boucle de réessai. Vous pourriez penser que vous pouvez éviter les erreurs de clé en double ou les mises à jour perdues avec SQL intelligent, mais vous ne pouvez pas. Vous devez vérifier le nombre de lignes ou gérer les erreurs de clé en double (selon l'approche choisie) et réessayer.
Veuillez ne pas lancer votre propre solution pour cela. Comme pour la mise en file d'attente des messages, c'est probablement faux.
Upsert en vrac avec serrure
Parfois, vous souhaitez effectuer une mise à niveau groupée, dans laquelle vous disposez d'un nouvel ensemble de données que vous souhaitez fusionner avec un ancien ensemble de données existant. Ceci est beaucoup plus efficace que les sauts de rangs individuels et devrait être préféré chaque fois que cela est possible.
Dans ce cas, vous suivez généralement le processus suivant:
CREATE
uneTEMPORARY
tableCOPY
ou insérez en masse les nouvelles données dans la table temporaireLOCK
la table cibleIN EXCLUSIVE MODE
. Cela permet à d'autres transactionsSELECT
, mais sans apporter de modifications à la table.Faites un
UPDATE ... FROM
des enregistrements existants en utilisant les valeurs de la table temporaire;Faites une
INSERT
des lignes qui n'existent pas déjà dans la table cible;COMMIT
, libérant le verrou.Par exemple, pour l'exemple donné dans la question, en utilisant plusieurs valeurs
INSERT
pour remplir la table temporaire:Lecture connexe
MERGE
sur le wiki PostgreSQLEt alors
MERGE
?Le standard SQL
MERGE
a en fait une sémantique de concurrence d'accès mal définie et ne convient pas pour la mise à jour sans verrouiller d'abord une table.C'est une instruction OLAP vraiment utile pour la fusion de données, mais ce n'est pas en fait une solution utile pour l'upsert concurrentiel sécurisé. Il y a beaucoup de conseils aux personnes utilisant d'autres SGBD
MERGE
pour les upserts, mais c'est en fait faux.Autres DB:
INSERT ... ON DUPLICATE KEY UPDATE
dans MySQLMERGE
de MS SQL Server (mais voir ci-dessus lesMERGE
problèmes)MERGE
d'Oracle (mais voir ci-dessus à propos desMERGE
problèmes)la source
MERGE
pour SQL Server et Oracle sont incorrectes et sujettes aux conditions de concurrence, comme indiqué ci-dessus. Vous aurez besoin d'examiner spécifiquement chaque SGBD pour savoir comment les gérer, je ne peux vraiment offrir que des conseils sur PostgreSQL. La seule façon de faire un upsert multi-lignes en toute sécurité sur PostgreSQL sera de prendre en charge le upsert natif sur le serveur principal.J'essaie de contribuer avec une autre solution pour le problème d'insertion unique avec les versions antérieures à 9.5 de PostgreSQL. L'idée est simplement d'essayer d'effectuer d'abord l'insertion, et dans le cas où l'enregistrement est déjà présent, de le mettre à jour:
Notez que cette solution ne peut être appliquée que s'il n'y a aucune suppression de lignes de la table .
Je ne connais pas l'efficacité de cette solution, mais elle me semble assez raisonnable.
la source
insert on update
Voici quelques exemples pour
insert ... on conflict ...
( pg 9.5+ ):la source
SQLAlchemy upsert pour Postgres> = 9.5
Étant donné que le grand message ci-dessus couvre de nombreuses approches SQL différentes pour les versions de Postgres (non seulement non-9.5 comme dans la question), je voudrais ajouter comment le faire dans SQLAlchemy si vous utilisez Postgres 9.5. Au lieu d'implémenter votre propre upsert, vous pouvez également utiliser les fonctions de SQLAlchemy (qui ont été ajoutées dans SQLAlchemy 1.1). Personnellement, je recommanderais de les utiliser si possible. Non seulement pour des raisons de commodité, mais aussi parce qu'il permet à PostgreSQL de gérer toutes les conditions de concurrence qui pourraient se produire.
Publication croisée d'une autre réponse que j'ai donnée hier ( https://stackoverflow.com/a/44395983/2156909 )
SQLAlchemy prend
ON CONFLICT
désormais en charge deux méthodeson_conflict_do_update()
eton_conflict_do_nothing()
:Copie de la documentation:
http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html?highlight=conflict#insert-on-conflict-upsert
la source
Testé sur Postgresql 9.3
la source
SERIALIZABLE
isolement, vous obtiendriez un abandon avec un échec de sérialisation, sinon vous obtiendriez probablement une violation unique. Ne réinventez pas l'upert, la réinvention sera fausse. UtilisezINSERT ... ON CONFLICT ...
. Si votre PostgreSQL est trop ancien, mettez-le à jour.INSERT ... ON CLONFLICT ...
n'est pas destiné au chargement en masse. De votre message, l'LOCK TABLE testtable IN EXCLUSIVE MODE;
intérieur d'un CTE est une solution de contournement pour obtenir des choses atomiques. Non ?insert ... where not exists ...
ou similaire, bien sûr.Depuis que cette question a été fermée, je poste ici pour savoir comment vous le faites en utilisant SQLAlchemy. Par récursivité, il réessaye une insertion en masse ou une mise à jour pour lutter contre les conditions de course et les erreurs de validation.
D'abord les importations
Maintenant, quelques fonctions d'assistance
Et enfin la fonction upsert
Voici comment vous l'utilisez
L'avantage que cela présente
bulk_save_objects
est qu'il peut gérer les relations, la vérification des erreurs, etc. lors de l'insertion (contrairement aux opérations en bloc ).la source
SERIALIZABLE
transactions et gérer les échecs de sérialisation, mais c'est lent. Vous avez besoin de la gestion des erreurs et d'une boucle de nouvelle tentative. Voir ma réponse et la section «lectures connexes».