web-dev-qa-db-fra.com

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

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

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

124
gpilotino

Essayez de faire une mise à jour. S'il ne modifie aucune ligne, cela signifie qu'elle n'existe pas, faites une insertion. Évidemment, vous faites cela dans une transaction.

Vous pouvez bien sûr envelopper cette fonction dans une fonction si vous ne voulez pas mettre le code supplémentaire du côté client. Vous avez également besoin d'une boucle pour la condition de course très rare dans cette pensée.

Il existe un exemple dans la documentation: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html , exemple 40-2 tout en bas.

C'est généralement le moyen le plus simple. Vous pouvez faire de la magie avec des règles, mais cela risque d'être beaucoup plus compliqué. Je recommanderais l'approche intégrale sur cette journée.

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, vous feriez mieux de la scinder en deux requêtes, une pour INSERT et l'autre pour UPDATE (en tant que jointure/sous-sélection appropriée, bien sûr - inutile d'écrire votre commande principale). filtrer deux fois)

31
Magnus Hagander

Avec PostgreSQL 9.5, ceci est maintenant fonctionnalité native (comme MySQL a depuis plusieurs années):

INSERT ... SUR UN CONFLIT, NE RIEN/METTRE À JOUR ("UPSERT")

9.5 apporte un soutien pour les opérations "UPSERT". INSERT est étendu pour accepter une clause ON CONFLICT DO UPDATE/IGNORE. Cette clause spécifie une action alternative à prendre en cas de violation supposée dupliquée.

...

Autre exemple de nouvelle syntaxe:

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1) 
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;
143
warren

Edit: au cas où vous auriez manqué réponse de Warren , PG9.5 l’a maintenant en mode natif; le temps de mettre à niveau!


En s'appuyant sur la réponse de Bill Karwin, précisez à quoi ressemblerait une approche basée sur des règles (transfert à partir d'un autre schéma de la même base de données 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 à toutes les opérations INSERT jusqu'à ce que la règle soit supprimée, donc pas tout à fait ad hoc.

96
EoghanM

Pour obtenir la logique insert ignore, vous pouvez faire quelque chose comme ci-dessous. J'ai trouvé que l'insertion simplement à partir d'une instruction select de valeurs littérales fonctionnait mieux. Vous pouvez ensuite masquer les clés en double avec une clause NOT EXISTS. Pour obtenir la mise à jour sur la logique en double, je soupçonne 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)
)
23
Keyo

Pour ceux d'entre vous qui ont Postgres 9.5 ou supérieur, la nouvelle syntaxe ON CONFLICT DO RING devrait fonctionner:

INSERT INTO target_table (field_one, field_two, field_three ) 
SELECT field_one, field_two, field_three
FROM source_table
ON CONFLICT (field_one) DO NOTHING;

Pour ceux d'entre nous qui ont une version antérieure, cette jointure de droits fonctionnera à la place:

INSERT INTO target_table (field_one, field_two, field_three )
SELECT source_table.field_one, source_table.field_two, source_table.field_three
FROM source_table 
LEFT JOIN target_table ON source_table.field_one = target_table.field_one
WHERE target_table.field_one IS NULL;
23
hanmari
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')
20
user2342158

On dirait que PostgreSQL supporte un objet de schéma appelé règle .

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

Vous pouvez créer une règle ON INSERT pour une table donnée, le faisant NOTHING si une ligne existe avec la valeur de clé primaire donnée, ou bien le faire faire un UPDATE au lieu du INSERT si la ligne existe avec la valeur de clé primaire donnée.

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

12
Bill Karwin

Comme @hanmari l'a mentionné dans son commentaire. lors de l'insertion dans une table postgres, le on conflict (..) ne rien faire est le meilleur code à utiliser pour ne pas insérer de données dupliquées .:

query = "INSERT INTO db_table_name(column_name)
         VALUES(%s) ON CONFLICT (column_name) DO NOTHING;"

La ligne de code ON CONFLICT permettra à l'instruction insert d'insérer des lignes de données. Le code de requête et de valeurs est un exemple de date insérée d'un fichier Excel dans une table postgres db. J'ai des contraintes ajoutées à une table postgres que j'utilise pour m'assurer que le champ ID est unique. Au lieu d'exécuter une suppression sur des lignes de données identiques, j'ajoute une ligne de code SQL qui renumérote la colonne ID commençant à 1. Exemple:

q = 'ALTER id_column serial RESTART WITH 1'

Si mes données ont un champ ID, je ne l'utilise pas comme ID principal/ID de série, je crée une colonne ID et je le configure en série. J'espère que cette information est utile à tout le monde. * Je n'ai pas de diplôme universitaire en développement/codage de logiciels. Tout ce que je sais en codage, j'étudie seul.

3
Yankeeownz

Cette solution évite d'utiliser des règles:

BEGIN
   INSERT INTO tableA (unique_column,c2,c3) VALUES (1,2,3);
EXCEPTION 
   WHEN unique_violation THEN
     UPDATE tableA SET c2 = 2, c3 = 3 WHERE unique_column = 1;
END;

mais cela présente un inconvénient en termes de performances (voir PostgreSQL.org ):

Un bloc contenant une clause EXCEPTION est nettement plus coûteux à entrer et à sortir qu'un bloc sans un. Par conséquent, n'utilisez pas EXCEPTION sans nécessité.

2
NumberFour

En vrac, vous pouvez toujours supprimer la ligne avant l'insertion. La suppression d’une ligne qui n’existe pas ne provoque pas d’erreur, elle est donc ignorée en toute sécurité.

1
David Noriega