165 votes

comment émuler "insert ignore" et "on duplicate key update" (sql merge) avec postgresql ?

Certains serveurs SQL ont une fonction où INSERT est ignoré s'il viole une contrainte de clé primaire/unique. Par exemple, MySQL a INSERT IGNORE .

Quelle est la meilleure façon d'émuler INSERT IGNORE y ON DUPLICATE KEY UPDATE avec PostgreSQL ?

0 votes

0 votes

6 votes

Depuis la version 9.5, c'est possible en mode natif : stackoverflow.com/a/34639631/4418

101voto

EoghanM Points 4383

Edit : au cas où vous auriez manqué réponse de warren PG9.5 est maintenant doté de cette fonctionnalité en mode natif ; il est temps de faire une mise à jour !


En s'appuyant sur la réponse de Bill Karwin, expliquer à quoi ressemblerait une approche basée sur des règles (transfert à partir d'un autre schéma dans la même BD, et avec une clé primaire à plusieurs colonnes) :

CREATE RULE "my_table_on_duplicate_ignore" AS ON INSERT TO "my_table"
  WHERE EXISTS(SELECT 1 FROM my_table 
                WHERE (pk_col_1, pk_col_2)=(NEW.pk_col_1, NEW.pk_col_2))
  DO INSTEAD NOTHING;
INSERT INTO my_table SELECT * FROM another_schema.my_table WHERE some_cond;
DROP RULE "my_table_on_duplicate_ignore" ON "my_table";

Remarque : La règle s'applique à tous les INSERT jusqu'à ce que la règle soit abandonnée, donc pas tout à fait ad hoc.

0 votes

@sema tu veux dire si another_schema.my_table contient des doublons selon les contraintes de my_table ?

2 votes

@EoghanM J'ai testé la règle dans postgresql 9.3 et je pouvais toujours insérer des doublons avec des instructions d'insertion de lignes multiples comme par exemple INSERT INTO "ma_table" (a,b),(a,b) ; (En supposant que la ligne (a,b) n'existait pas encore dans "ma_table").

0 votes

@sema, gotcha - cela doit signifier que la règle est exécutée au départ sur toutes les données à insérer, et non pas réexécutée après l'insertion de chaque ligne. Une approche serait d'insérer d'abord vos données dans une autre table temporaire qui n'a pas de contraintes, et ensuite de faire INSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;

40voto

Magnus Hagander Points 8671

Essayez de faire un UPDATE. Si aucune ligne n'est modifiée, cela signifie qu'elle n'existait pas, alors faites une insertion. Évidemment, vous faites cela à l'intérieur d'une transaction.

Vous pouvez bien sûr intégrer cette opération dans une fonction si vous ne souhaitez pas ajouter de code supplémentaire du côté du client. Vous avez également besoin d'une boucle pour les très rares conditions de course dans ce raisonnement.

Il y a un exemple de ceci dans la documentation : http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html l'exemple 40-2 juste en bas.

C'est généralement le moyen le plus simple. Vous pouvez faire de la magie avec des règles, mais ce sera probablement beaucoup plus désordonné. Je recommanderais volontiers l'approche "envelopper dans la fonction".

Cela fonctionne pour les valeurs à une seule ligne ou à quelques lignes. Si vous traitez de grandes quantités de lignes, par exemple à partir d'une sous-requête, il est préférable de la diviser en deux requêtes, une pour INSERT et une pour UPDATE (avec une jointure/sous-sélection appropriée bien sûr - pas besoin d'écrire deux fois votre filtre principal).

4 votes

"Si vous avez affaire à de grandes quantités de lignes" c'est exactement mon cas. Je veux mettre à jour/insérer des lignes en masse et avec mysql je peux le faire avec une seule requête sans aucune boucle. Maintenant je me demande si c'est possible avec postgresql aussi : utiliser une seule requête pour mettre à jour OU insérer en masse. Vous dites : Vous dites : "vous êtes mieux de le diviser en deux requêtes, une pour INSERT et une pour UPDATE" mais comment puis-je faire une insertion qui ne jette pas d'erreurs sur les clés dupliquées ? (par exemple, "INSERT IGNORE").

4 votes

Magnus voulait dire que vous utilisez une requête comme celle-ci : "démarrer la transaction ; créer une table temporaire temporary_table comme select * from test where false ; copier temporary_table from 'data_file.csv' ; verrouiller la table test ; mettre à jour test set data=temporary_table.data from temporary_table where test.id=temporary_table.id ; insérer dans test select * from temporary_table where id not in (select id from test) as a"

36 votes

Mise à jour : Avec PostgreSQL 9.5, c'est maintenant aussi simple que INSERT ... ON CONFLICT DO NOTHING; . Voir aussi la réponse stackoverflow.com/a/34639631/2091700 .

27voto

Keyo Points 4138

Pour obtenir le insérer ignorer logique vous pouvez faire quelque chose comme ci-dessous. J'ai trouvé que le fait d'insérer simplement à partir d'une instruction de sélection de valeurs littérales fonctionnait le mieux, puis vous pouvez masquer les clés dupliquées avec une clause NOT EXISTS. Pour obtenir la logique de mise à jour des doublons, je pense qu'une boucle pl/pgsql serait nécessaire.

INSERT INTO manager.vin_manufacturer
(SELECT * FROM( VALUES
  ('935',' Citroën Brazil','Citroën'),
  ('ABC', 'Toyota', 'Toyota'),
  ('ZOM',' OM','OM')
  ) as tmp (vin_manufacturer_id, manufacturer_desc, make_desc)
  WHERE NOT EXISTS (
    --ignore anything that has already been inserted
    SELECT 1 FROM manager.vin_manufacturer m where m.vin_manufacturer_id = tmp.vin_manufacturer_id)
)

0 votes

Que se passe-t-il si tmp contient une ligne en double, ce qui peut arriver ?

0 votes

Vous pouvez toujours sélectionner avec le mot-clé distinct.

5 votes

À titre d'information, l'astuce "WHERE NOT EXISTS" ne fonctionne pas sur plusieurs transactions, car les différentes transactions ne peuvent pas voir les données nouvellement ajoutées par les autres transactions.

23voto

user2342158 Points 41
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')

0 votes

Quel est l'impact de plusieurs transactions qui tentent toutes de faire la même chose ? Est-il possible qu'entre l'exécution du where not exists et l'exécution de l'insertion, une autre transaction insère une ligne ? Et si Postgres peut empêcher cela, alors Postgres n'introduit-il pas un point de synchronisation entre toutes ces transactions lorsqu'elles atteignent ce point ?

0 votes

Cela ne fonctionne pas avec des transactions multiples, car les données nouvellement ajoutées ne sont pas visibles pour les autres transactions.

15voto

Bill Karwin Points 204877

Il semble que PostgreSQL supporte un objet de schéma appelé un règle .

http://www.postgresql.org/docs/current/static/rules-update.html

Vous pourriez créer une règle ON INSERT pour une table donnée, en lui faisant faire NOTHING si une rangée existe avec la valeur de la clé primaire donnée, ou sinon en faisant un UPDATE au lieu de la INSERT si une ligne existe avec la valeur de la clé primaire donnée.

Je n'ai pas essayé moi-même, je ne peux donc pas parler d'expérience ou donner un exemple.

1 votes

Si j'ai bien compris, ces règles sont des déclencheurs qui sont exécutés chaque fois qu'une instruction est appelée. Que se passe-t-il si je veux appliquer la règle pour une seule requête ? Je dois créer la règle puis la détruire immédiatement ? (qu'en est-il des conditions de course ?)

3 votes

Oui, je me poserais aussi les mêmes questions. Le mécanisme de règle est la chose la plus proche que j'ai pu trouver dans PostgreSQL de l'INSERT IGNORE ou de l'ON DUPLICATE KEY UPDATE de MySQL. Si l'on cherche sur Google "postgresql on duplicate key update", on trouve d'autres personnes qui recommandent le mécanisme de règle, même si une règle s'applique à tout INSERT, et pas seulement sur une base ad hoc.

5 votes

PostgreSQL supporte le DDL transactionnel, ce qui signifie que si vous créez une règle et la déposez dans une seule transaction, la règle n'aura jamais été visible en dehors de cette transaction (et n'aura donc jamais eu d'effet en dehors de celle-ci).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X