Question

Il y a quelques mois, j'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 l'ai maintenant basculé sur PostgreSQL et apparemment ce n'est pas correct. Il fait référence à toutes les tables correctes, donc je suppose que c'est une question de différents mots-clés utilisés mais je ne suis pas sûr où dans la documentation de PostgreSQL cette question est traitée.

Pour clarifier les choses, je veux insérer plusieurs choses et si elles existent déjà pour les mettre à jour.

Était-ce utile?

La solution

PostgreSQL depuis la version 9.5 a UPSERT syntaxe, avec < strong> ON clause de conflit. avec la syntaxe suivante (similaire à MySQL)

INSERT INTO the_table (id, column_1, column_2) 
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
ON CONFLICT (id) DO UPDATE 
  SET column_1 = excluded.column_1, 
      column_2 = excluded.column_2;

Recherche archives du groupe électronique de postgresql pour "upsert" conduit à la recherche un exemple de faire ce que vous voulez peut-être faire, dans le manuel :

  

Exemple 38-2. Exceptions avec UPDATE / INSERT

     

Cet exemple utilise la gestion des exceptions pour réaliser soit un UPDATE ou INSERT, selon le cas:

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        -- note that "a" must be unique
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- do nothing, and loop to try the UPDATE again
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

Il y a peut-être un exemple de la façon de le faire en vrac, en utilisant CTEs en 9.1 et au-dessus, dans la section noreferrer pirates liste de diffusion :

WITH foos AS (SELECT (UNNEST(%foo[])).*)
updated as (UPDATE foo SET foo.a = foos.a ... RETURNING foo.id)
INSERT INTO foo SELECT foos.* FROM foos LEFT JOIN updated USING(id)
WHERE updated.id IS NULL;

Voir de réponse a_horse_with_no_name pour un exemple plus clair.

Autres conseils

Attention:. Ce n'est pas sûre si elle est exécutée à partir de plusieurs sessions en même temps (voir mises en garde ci-dessous)


Une autre façon intelligente de faire une « UPSERT » dans postgresql est de faire deux instructions UPDATE / INSERT séquentielle qui sont chacun conçus pour réussir ou sans effet.

UPDATE table SET field='C', field2='Z' WHERE id=3;
INSERT INTO table (id, field, field2)
       SELECT 3, 'C', 'Z'
       WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);

La mise à jour réussir si une ligne avec « id = 3 » existe déjà, sinon il n'a pas d'effet.

L'INSERT ne réussira que si la ligne avec "id = 3" n'existe pas.

Vous pouvez combiner ces deux en une seule chaîne et de les exécuter à la fois avec une seule instruction SQL exécuter à partir de votre application. les exécuter ensemble dans une seule transaction est fortement recommandée.

Cela fonctionne très bien lorsqu'il est exécuté de manière isolée ou sur une table verrouillée, mais est soumis à la race des conditions qui signifient qu'il pourrait encore échouer avec clé doublon si une ligne est insérée en même temps, ou peut mettre fin sans ligne insérée lorsqu'une ligne est supprimé en même temps. Une transaction SERIALIZABLE sur PostgreSQL 9.1 ou supérieur traitera de manière fiable au prix d'un taux d'échec de sérialisation très élevé, ce qui signifie que vous devrez recommencer beaucoup. Voir pourquoi est upsert si compliqué , qui discute ce cas plus en détail.

Cette approche est soumis à des mises à jour perdus dans l'isolement de read committed à moins que l'application vérifie les comptes de ligne concernés et vérifie que soit le insert ou le update affecté un de ligne.

Avec PostgreSQL 9.1 cela peut être réalisé au moyen d'un CTE inscriptible ( expression de table commune ) :

WITH new_values (id, field1, field2) as (
  values 
     (1, 'A', 'X'),
     (2, 'B', 'Y'),
     (3, 'C', 'Z')

),
upsert as
( 
    update mytable m 
        set field1 = nv.field1,
            field2 = nv.field2
    FROM new_values nv
    WHERE m.id = nv.id
    RETURNING m.*
)
INSERT INTO mytable (id, field1, field2)
SELECT id, field1, field2
FROM new_values
WHERE NOT EXISTS (SELECT 1 
                  FROM upsert up 
                  WHERE up.id = new_values.id)

Voir ces entrées de blog:


Notez que cette solution ne pas éviter une violation de clé unique, mais il est vulnérable aux mises à jour perdues.
Voir suivi par Craig Ringer sur dba.stackexchange.com

Dans PostgreSQL 9.5 et plus récente, vous pouvez utiliser INSERT ... ON CONFLICT UPDATE.

Voir la documentation .

Un INSERT ... ON DUPLICATE KEY UPDATE MySQL peut être directement reformulé à un ON CONFLICT UPDATE. Ni est la syntaxe SQL standard, ils sont les deux extensions spécifiques à la base de données. Il y a de bonnes raisons MERGE n'a pas été utilisé pour cette , une nouvelle syntaxe n'a pas été créé juste pour le plaisir. (La syntaxe de MySQL a aussi des problèmes qui signifient qu'il n'a pas été adopté directement).

par exemple. configuration donnée:

CREATE TABLE tablename (a integer primary key, b integer, c integer);
INSERT INTO tablename (a, b, c) values (1, 2, 3);

la requête MySQL:

INSERT INTO tablename (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

devient:

INSERT INTO tablename (a, b, c) values (1, 2, 10)
ON CONFLICT (a) DO UPDATE SET c = tablename.c + 1;

Différences:

  • doit indiquer le nom de la colonne (ou le nom de contrainte unique) à utiliser pour le contrôle d'unicité. C'est le ON CONFLICT (columnname) DO

  • Le SET mot-clé doit être utilisé, comme si cela était une déclaration de UPDATE normale

Il a quelques fonctionnalités intéressantes aussi:

  • Vous pouvez avoir une clause de WHERE sur votre UPDATE (vous permettant de transformer efficacement ON CONFLICT UPDATE en ON CONFLICT IGNORE pour certaines valeurs)

  • Le proposée à des fins d'insertion de valeurs sont disponibles en tant que EXCLUDED de ligne variable, qui a la même structure que la table cible. Vous pouvez obtenir les valeurs d'origine dans le tableau en utilisant le nom de la table. Donc dans ce cas EXCLUDED.c sera 10 (parce que ce que nous avons essayé d'insérer) et "table".c sera 3 parce que c'est la valeur actuelle dans le tableau. Vous pouvez utiliser une ou l'autre dans les expressions SET et clause WHERE.

Pour des informations sur upsert voir Comment upsert (Merge, INSERT ... MISE À JOUR SUR duplicate) dans PostgreSQL?

Je cherchais la même chose quand je suis venu ici, mais l'absence d'une fonction générique « upsert » me botherd un peu, donc je pensais que vous pouvez simplement passer la mise à jour et insérer sql comme arguments sur cette fonction constituent le manuel

qui ressemblerait à ceci:

CREATE FUNCTION upsert (sql_update TEXT, sql_insert TEXT)
    RETURNS VOID
    LANGUAGE plpgsql
AS $$
BEGIN
    LOOP
        -- first try to update
        EXECUTE sql_update;
        -- check if the row is found
        IF FOUND THEN
            RETURN;
        END IF;
        -- not found so insert the row
        BEGIN
            EXECUTE sql_insert;
            RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- do nothing and loop
        END;
    END LOOP;
END;
$$;

et peut-être faire ce que vous vouliez d'abord faire, batch « upsert », vous pouvez utiliser Tcl pour diviser les sql_update et boucle les mises à jour individuelles, le coup de preformance sera très faible, voir http://archives.postgresql.org/pgsql-performance/2006-04/msg00557.php

le coût le plus élevé est la requête en cours d'exécution de votre code, du côté de la base de données le coût d'exécution est beaucoup plus petit

Il n'y a pas de commande simple de le faire.

L'approche la plus correcte est d'utiliser la fonction, comme celle de docs .

Une autre solution (bien que pas sûr) est de faire la mise à jour avec le retour, vérifiez les lignes ont été mises à jour, et insérez le reste d'entre eux

Quelque chose le long des lignes de:

update table
set column = x.column
from (values (1,'aa'),(2,'bb'),(3,'cc')) as x (id, column)
where table.id = x.id
returning id;

id en supposant: 2 a été renvoyé:

insert into table (id, column) values (1, 'aa'), (3, 'cc');

Bien sûr, il écopera tôt ou tard (dans un environnement en même temps), car il est la condition de course claire ici, mais le plus souvent cela fonctionnera.

Voici un plus et l'article plus complet sur la sujet .

Personnellement, je l'ai mis en place une « règle » jointe à l'instruction d'insertion. Supposons que vous aviez une table « dns » qui a enregistré hits dns par client sur une base par temps:

CREATE TABLE dns (
    "time" timestamp without time zone NOT NULL,
    customer_id integer NOT NULL,
    hits integer
);

Tu voulais être en mesure de re-insérer des lignes avec des valeurs mises à jour, ou les créer si elles n'existaient déjà. Détrompeur sur le customer_id et le temps. Quelque chose comme ceci:

CREATE RULE replace_dns AS 
    ON INSERT TO dns 
    WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time") 
            AND (dns.customer_id = new.customer_id)))) 
    DO INSTEAD UPDATE dns 
        SET hits = new.hits 
        WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));

Mise à jour: Il a le potentiel d'échouer si inserts simultanés se produisent, car il génère des exceptions unique_violation. Cependant, la transaction non terminée continuera et réussir, et il vous suffit de répéter l'opération terminée.

Cependant, s'il y a des tonnes d'inserts qui se produisent tout le temps, vous voulez mettre un verrou de table autour des instructions d'insertion: verrouillage SHARE ROW EXCLUSIVE empêchera toute opération qui pourrait insérer, supprimer ou lignes de mise à jour dans votre table cible. Cependant, les mises à jour qui ne mettent pas à jour la clé unique sont en sécurité, donc si vous ne opération cela, utilisez les verrous consultatifs à la place.

En outre, la commande COPY ne pas utiliser les règles, donc si vous insérez avec copie, vous devrez utiliser des déclencheurs à la place.

I sur mesure "upsert" fonction ci-dessus, si vous voulez insérer et REPLACE:

`

 CREATE OR REPLACE FUNCTION upsert(sql_insert text, sql_update text)

 RETURNS void AS
 $BODY$
 BEGIN
    -- first try to insert and after to update. Note : insert has pk and update not...

    EXECUTE sql_insert;
    RETURN;
    EXCEPTION WHEN unique_violation THEN
    EXECUTE sql_update; 
    IF FOUND THEN 
        RETURN; 
    END IF;
 END;
 $BODY$
 LANGUAGE plpgsql VOLATILE
 COST 100;
 ALTER FUNCTION upsert(text, text)
 OWNER TO postgres;`

Et après l'exécution, faire quelque chose comme ceci:

SELECT upsert($$INSERT INTO ...$$,$$UPDATE... $$)

est important de mettre deux dollars par des virgules pour éviter les erreurs du compilateur

  • vérifier la vitesse ...

Tout comme réponse la plus populaire, mais fonctionne un peu plus vite:

WITH upsert AS (UPDATE spider_count SET tally=1 WHERE date='today' RETURNING *)
INSERT INTO spider_count (spider, tally) SELECT 'Googlebot', 1 WHERE NOT EXISTS (SELECT * FROM upsert)

(source: http: //www.the-art-of- web.com/sql/upsert/ )

J'ai le même problème pour la gestion des paramètres de compte en tant que paires nom-valeur. Les critères de conception est que les différents clients peuvent avoir différents ensembles de paramètres.

Ma solution, similaire à GTM est d'effacer en vrac et remplacer, génération de l'enregistrement de fusion dans votre application.

est assez pare-balles, la plate-forme indépendante et comme il n'y a jamais plus de 20 paramètres par client, cela est seulement 3 charge assez bas appels db -. Probablement la méthode la plus rapide

L'alternative de la mise à jour des lignes individuelles - la vérification des exceptions, puis l'insertion - ou une combinaison est un code hideux, lent et se casse souvent parce que (comme mentionné ci-dessus) non standards gestion des exceptions SQL passe de db à db - ou même une version à .

 #This is pseudo-code - within the application:
 BEGIN TRANSACTION - get transaction lock
 SELECT all current name value pairs where id = $id into a hash record
 create a merge record from the current and update record
  (set intersection where shared keys in new win, and empty values in new are deleted).
 DELETE all name value pairs where id = $id
 COPY/INSERT merged records 
 END TRANSACTION

Selon le documentation PostgreSQL de la déclaration INSERT , la manipulation le cas de ON DUPLICATE KEY n'est pas pris en charge. Cette partie de la syntaxe est une extension MySQL propriétaire.

CREATE OR REPLACE FUNCTION save_user(_id integer, _name character varying)
  RETURNS boolean AS
$BODY$
BEGIN
    UPDATE users SET name = _name WHERE id = _id;
    IF FOUND THEN
        RETURN true;
    END IF;
    BEGIN
        INSERT INTO users (id, name) VALUES (_id, _name);
    EXCEPTION WHEN OTHERS THEN
            UPDATE users SET name = _name WHERE id = _id;
        END;
    RETURN TRUE;
END;

$BODY$
  LANGUAGE plpgsql VOLATILE STRICT

J'utilise cette fusion de fonction

CREATE OR REPLACE FUNCTION merge_tabla(key INT, data TEXT)
  RETURNS void AS
$BODY$
BEGIN
    IF EXISTS(SELECT a FROM tabla WHERE a = key)
        THEN
            UPDATE tabla SET b = data WHERE a = key;
        RETURN;
    ELSE
        INSERT INTO tabla(a,b) VALUES (key, data);
        RETURN;
    END IF;
END;
$BODY$
LANGUAGE plpgsql

Pour la fusion de petits ensembles, en utilisant la fonction ci-dessus est très bien. Toutefois, si vous fusionnez de grandes quantités de données, je vous suggère de regarder dans http: //mbk.projects.postgresql .org

La meilleure pratique actuelle que je suis au courant est:

  1. Copier / nouvelles données mises à jour dans la table temporaire (vous, ou vous pouvez faire INSERT si le coût est ok)
  2. Acquisition de verrouillage [facultatif] (conseil est préférable de verrous de table, OMI)
  3. Fusion. (La partie amusante)

UPDATE retourne le nombre de lignes modifiées. Si vous utilisez JDBC (Java), vous pouvez vérifier cette valeur contre 0 et, si aucune ligne n'a été affectée, le feu INSERT au lieu. Si vous utilisez un autre langage de programmation, peut-être le nombre de lignes modifiées peut encore être obtenu, la documentation de vérification.

Cela peut ne pas être aussi élégant, mais vous avez beaucoup plus simple SQL qui est plus trivial d'utiliser du code d'appel. D'autres termes, si vous écrivez le script de dix lignes en PL / PSQL, vous devriez probablement avoir un test unitaire d'un ou d'une autre nature juste pour elle seule.

Modifier Cela ne fonctionne pas comme prévu. Contrairement à la réponse acceptée, cela produit des violations clés uniques lorsque deux processus appellent à plusieurs reprises upsert_foo simultanément.

Eureka! Je me suis trouvé un moyen de le faire dans une requête: utiliser UPDATE ... RETURNING pour tester si des lignes ont été affectées:

CREATE TABLE foo (k INT PRIMARY KEY, v TEXT);

CREATE FUNCTION update_foo(k INT, v TEXT)
RETURNS SETOF INT AS $$
    UPDATE foo SET v = $2 WHERE k = $1 RETURNING $1
$$ LANGUAGE sql;

CREATE FUNCTION upsert_foo(k INT, v TEXT)
RETURNS VOID AS $$
    INSERT INTO foo
        SELECT $1, $2
        WHERE NOT EXISTS (SELECT update_foo($1, $2))
$$ LANGUAGE sql;

Le UPDATE doit être fait dans une procédure distincte parce que, malheureusement, cela est une erreur de syntaxe:

... WHERE NOT EXISTS (UPDATE ...)

Maintenant, il fonctionne comme vous le souhaitez:

SELECT upsert_foo(1, 'hi');
SELECT upsert_foo(1, 'bye');
SELECT upsert_foo(3, 'hi');
SELECT upsert_foo(3, 'bye');
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top