it-swarm-eu.dev

Idiomatický způsob implementace UPSERT v PostgreSQL

Četl jsem o různých implementacích UPSERT v PostgreSQL, ale všechna tato řešení jsou relativně stará nebo relativně exotická (například pomocí zapisovatelného CTE )).

A nejsem vůbec odborník na psql, abych okamžitě zjistil, zda jsou tato řešení stará, protože jsou dobře doporučována nebo jsou (dobře, téměř všechna z nich jsou) jen příklady hraček, které nejsou vhodné pro použití při výrobě.

Jaký je nejbezpečnější způsob implementace UPSERT v PostgreSQL?

39
shabunc

PostgreSQL má nyní UPSERT .


Upřednostňovaná metoda podle podobná otázka StackOverflow je v současné době následující:

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

[~ # ~] aktualizace [~ # ~] (2015 -08-20):

Nyní existuje oficiální implementace pro zpracování upserts pomocí ON CONFLICT DO UPDATE (oficiální dokumentace). V době psaní tohoto článku je tato funkce aktuálně umístěna v PostgreSQL 9.5 Alpha 2, který je k dispozici ke stažení zde: Postgres source adresáře .

Zde je příklad, za předpokladu item_id je váš primární klíč:

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

Původní příspěvek ...

Zde je implementace, na kterou jsem přišel, když jsem chtěl získat přehled o tom, zda došlo k vložení nebo aktualizaci.

Definice upsert_data znamená sloučit hodnoty do jednoho zdroje, namísto toho, aby bylo nutné dvakrát zadat cenu a item_id: Jednou pro aktualizaci, znovu pro vložení.

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

Pokud se vám nelíbí použití upsert_data, zde je alternativní implementace:

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

To vám dá vědět, zda k vložení nebo aktualizaci došlo:

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" );

Dojde-li k aktualizaci, dostanete vložku 0, jinak vloží 1 nebo chybu.

0
John Fawcett