วิธีการใช้ UPSERT ใน PostgreSQL


40

ฉันได้อ่านเกี่ยวกับUPSERTการใช้งานที่แตกต่างกันใน PostgreSQL แต่โซลูชันเหล่านี้ทั้งหมดค่อนข้างเก่าหรือค่อนข้างแปลกใหม่ (โดยใช้CTE ที่เขียนได้ )

และฉันก็ไม่ใช่ผู้เชี่ยวชาญ psql เลยที่จะรู้ได้ทันทีว่าโซลูชันเหล่านี้เก่าหรือไม่เพราะพวกเขาได้รับการแนะนำอย่างดีหรือพวกเขา (ดีเกือบทั้งหมดเป็น) เพียงตัวอย่างของเล่นที่ไม่เหมาะสมกับการใช้งานจริง

อะไรคือวิธีที่ปลอดภัยที่สุดในการใช้ UPSERT ใน PostgreSQL

คำตอบ:


23

PostgreSQL ตอนนี้มีUPSERTแล้ว


วิธีที่ต้องการตามคำถาม StackOverflow ที่คล้ายกันในปัจจุบันมีดังต่อไปนี้:

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

7
ฉันต้องการใช้ CTE แบบเขียนได้: stackoverflow.com/a/8702291/330315
a_horse_with_no_name

ข้อดีของ CTE ที่เขียนได้เทียบกับฟังก์ชันคืออะไร
François Beausoleil

1
@ Françoisสำหรับสิ่งหนึ่งความเร็ว การใช้ CTE จะทำให้คุณกดฐานข้อมูลหนึ่งครั้ง ทำแบบนี้คุณอาจตีสองครั้งขึ้นไป นอกจากนี้เครื่องมือเพิ่มประสิทธิภาพไม่สามารถเพิ่มประสิทธิภาพขั้นตอน pl / pgsql ได้อย่างมีประสิทธิภาพเช่นเดียวกับรหัส SQL บริสุทธิ์
Adam Mackler

1
@ Françoisสำหรับอีกสิ่งหนึ่งการทำงานพร้อมกัน เนื่องจากตัวอย่างข้างต้นมีคำสั่ง SQL หลายคำสั่งคุณต้องกังวลเกี่ยวกับสภาพการแข่งขัน (เหตุผลของการวน klugey) คำสั่ง SQL เดี่ยวจะเป็นอะตอมมิก ดูลิงค์นี้
Adam Mackler

1
@ FrançoisBeausoleilดูที่นี่และที่นี่สำหรับเหตุผล โดยทั่วไปโดยไม่ต้องลองลูปคุณต้องทำให้เป็นอันดับหรือคุณมีความเป็นไปได้ของความล้มเหลวเนื่องจากสภาพการแข่งขันโดยธรรมชาติ
แจ็คดักลาส

27

อัปเดต (2015-08-20):

ขณะนี้มีการดำเนินการอย่างเป็นทางการสำหรับการจัดการ upserts ผ่านการใช้งานON CONFLICT DO UPDATE(เอกสารอย่างเป็นทางการ) ในขณะที่เขียนนี้คุณลักษณะนี้อาศัยอยู่ในปัจจุบัน PostgreSQL 9.5 อัลฟ่า 2 ซึ่งสามารถดาวน์โหลดได้ที่นี่: ไดเรกทอรีแหล่ง Postgres

นี่คือตัวอย่างสมมติว่าitem_idเป็นคีย์หลักของคุณ:

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

โพสต์ต้นฉบับ ...

นี่คือการติดตั้งที่ฉันใช้เมื่อต้องการจะมองเห็นว่ามีการแทรกหรือการอัพเดตเกิดขึ้นหรือไม่

คำจำกัดความของupsert_dataคือการรวมค่าลงในทรัพยากรเดียวแทนที่จะต้องระบุราคาและ item_id สองครั้ง: หนึ่งครั้งสำหรับการอัปเดตอีกครั้งสำหรับการแทรก

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

หากคุณไม่ชอบการใช้งานupsert_dataนี่คือการใช้งานแบบอื่น:

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

มันทำงานได้อย่างไร
jb

1
@jb ไม่เหมือนที่ฉันต้องการ คุณจะเห็นการลงโทษอย่างมีนัยสำคัญเมื่อเทียบกับการแสดงเม็ดมีดตรง อย่างไรก็ตามสำหรับชุดเล็ก ๆ (พูด 1,000 หรือน้อยกว่า) ตัวอย่างนี้ควรทำงานได้ดี
Joshua Burns

0

วิธีนี้จะช่วยให้คุณทราบว่ามีการแทรกหรืออัพเดทเกิดขึ้น

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

หากการปรับปรุงเกิดขึ้นคุณจะได้รับการแทรก 0 มิฉะนั้นจะแทรก 1 หรือข้อผิดพลาด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.