web-dev-qa-db-fra.com

Manière idiomatique d'implémenter UPSERT dans PostgreSQL

J'ai lu différentes implémentations UPSERT dans PostgreSQL, mais toutes ces solutions sont relativement anciennes ou relativement exotiques (en utilisant CTE inscriptible , par exemple).

Et je ne suis pas du tout un expert en psql pour savoir immédiatement si ces solutions sont anciennes parce qu'elles sont bien recommandées ou si elles sont (enfin, presque toutes) des exemples de jouets qui ne conviennent pas à la production.

Quelle est la manière la plus sûre pour les threads d'implémenter UPSERT dans PostgreSQL?

39
shabunc

PostgreSQL a maintenant UPSERT .


La méthode préférée selon ne question StackOverflow similaire est actuellement la suivante:

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
        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');
22
Leigh Riffel

[~ # ~] mise à jour [~ # ~] (2015-08-20):

Il existe maintenant une implémentation officielle pour la gestion des upserts grâce à l'utilisation de ON CONFLICT DO UPDATE (documentation officielle). Au moment d'écrire ces lignes, cette fonctionnalité réside actuellement dans PostgreSQL 9.5 Alpha 2, qui est disponible en téléchargement ici: répertoires source de Postgres .

Voici un exemple, en supposant que item_id est votre clé primaire:

INSERT INTO my_table
    (item_id, price)
VALUES
    (123456, 10.99)
ON
    CONFLICT (item_id)
DO UPDATE SET
    price = EXCLUDED.price

Message d'origine ...

Voici une implémentation sur laquelle je suis arrivé lorsque je voulais gagner en visibilité sur l'insertion ou la mise à jour.

La définition de upsert_data consiste à consolider les valeurs en une seule ressource, plutôt que d'avoir à spécifier le prix et item_id deux fois: une fois pour la mise à jour, de nouveau pour l'insert.

WITH upsert_data AS (
    SELECT
    '19.99'::numeric(10,2) AS price,
    'abcdefg'::character varying AS item_id
),
update_outcome AS (
    UPDATE pricing_tbl
    SET price = upsert_data.price
    FROM upsert_data
    WHERE pricing_tbl.item_id = upsert_data.item_id
    RETURNING 'update'::text AS action, item_id
),
insert_outcome AS (
    INSERT INTO
        pricing_tbl
    (price, item_id)
    SELECT
        upsert_data.price AS price,
        upsert_data.item_id AS item_id
    FROM upsert_data
    WHERE NOT EXISTS (SELECT item_id FROM update_outcome LIMIT 1)
    RETURNING 'insert'::text AS action, item_id
)
SELECT * FROM update_outcome UNION ALL SELECT * FROM insert_outcome

Si vous n'aimez pas l'utilisation de upsert_data, voici une implémentation alternative:

WITH update_outcome AS (
    UPDATE pricing_tbl
    SET price = '19.99'
    WHERE pricing_tbl.item_id = 'abcdefg'
    RETURNING 'update'::text AS action, item_id
),
insert_outcome AS (
    INSERT INTO
        pricing_tbl
    (price, item_id)
    SELECT
        '19.99' AS price,
        'abcdefg' AS item_id
    WHERE NOT EXISTS (SELECT item_id FROM update_outcome LIMIT 1)
    RETURNING 'insert'::text AS action, item_id
)
SELECT * FROM update_outcome UNION ALL SELECT * FROM insert_outcome
27
Joshua Burns

Cela vous permettra de savoir si l'insertion ou la mise à jour s'est produite:

with "update_items" as (
  -- Update statement here
  update items set price = 3499, name = 'Uncle Bob'
  where id = 1 returning *
)
-- Insert statement here
insert into items (price, name)
-- But make sure you put your values like so
select 3499, 'Uncle Bob'
where not exists ( select * from "update_items" );

Si la mise à jour se produit, vous obtiendrez un insert 0, sinon insérez 1 ou une erreur.

0
John Fawcett