it-swarm.asia

Cara idiomatis untuk mengimplementasikan UPSERT di PostgreSQL

Saya telah membaca tentang implementasi UPSERT yang berbeda di PostgreSQL, tetapi semua solusi ini relatif lama atau relatif eksotis (menggunakan CTE yang dapat ditulis , misalnya).

Dan saya bukan ahli psql sama sekali untuk mencari tahu segera, apakah solusi ini sudah lama karena mereka direkomendasikan atau mereka (well, hampir semuanya) hanya contoh mainan yang tidak sesuai untuk penggunaan produksi.

Apa cara yang paling aman untuk mengimplementasikan UPSERT di PostgreSQL?

39
shabunc

PostgreSQL sekarang memiliki UPSERT .


Metode yang disukai sesuai pertanyaan StackOverflow serupa saat ini adalah sebagai berikut:

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

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

Sekarang ada implementasi resmi untuk menangani upsert melalui penggunaan ON CONFLICT DO UPDATE (dokumentasi resmi). Pada saat penulisan ini, fitur ini saat ini berada di PostgreSQL 9.5 Alpha 2, yang tersedia untuk diunduh di sini: Direktori sumber Postgres .

Berikut ini sebuah contoh, dengan asumsi item_id adalah Kunci Utama Anda:

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

Pos Asli ...

Ini adalah implementasi yang saya dapatkan ketika ingin mendapatkan visibilitas ke apakah penyisipan atau pembaruan terjadi.

Definisi upsert_data adalah untuk menggabungkan nilai menjadi satu sumber daya, daripada harus menentukan harga dan item_id dua kali: Sekali untuk pembaruan, sekali lagi untuk memasukkan.

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

Jika Anda tidak menyukai penggunaan upsert_data, berikut ini adalah implementasi alternatif:

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

Ini akan memberi tahu Anda apakah penyisipan atau pembaruan terjadi:

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

Jika pembaruan terjadi, Anda akan mendapatkan sisipan 0, jika tidak masukkan 1 atau kesalahan.

0
John Fawcett