Cách thành ngữ để triển khai UPSERT trong PostgreSQL


40

Tôi đã đọc về các UPSERTtriển khai khác nhau trong PostgreSQL, nhưng tất cả các giải pháp này đều tương đối cũ hoặc tương đối kỳ lạ ( ví dụ sử dụng CTE có thể ghi được ).

Và tôi hoàn toàn không phải là một chuyên gia về psql để tìm hiểu ngay lập tức, liệu các giải pháp này đã cũ vì chúng được khuyến nghị hay chúng (tốt, hầu hết tất cả chúng đều) chỉ là ví dụ đồ chơi không phù hợp với việc sử dụng sản xuất.

Cách an toàn nhất cho chủ đề để triển khai UPSERT trong PostgreSQL là gì?

Câu trả lời:


23

PostgreSQL hiện có UPSERT .


Phương thức ưa thích theo câu hỏi StackOverflow tương tự hiện tại như sau:

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
Tôi muốn sử dụng một CTE có thể ghi: stackoverflow.com/a/8702291/330315
a_horse_with_no_name

Lợi thế của CTE có thể ghi so với chức năng là gì?
François Beausoleil

1
@ François cho một điều, tốc độ. Sử dụng CTE bạn nhấn cơ sở dữ liệu một lần. Làm theo cách này bạn có thể đánh nó hai lần trở lên. Ngoài ra, trình tối ưu hóa không thể tối ưu hóa các thủ tục pl / pssql hiệu quả như mã SQL thuần túy.
Adam Mackler

1
@ François Đối với một điều khác, đồng thời. Vì ví dụ trên có nhiều câu lệnh SQL, bạn phải lo lắng về các điều kiện chủng tộc (lý do cho vòng lặp klugey). Một câu lệnh SQL sẽ là nguyên tử. Xem liên kết này
Adam Mackler 23/11/13

1
@ FrançoisBeausoleil xem tại đâytại đây để biết lý do. Về cơ bản không có vòng lặp thử lại, bạn có thể phải tuần tự hóa hoặc bạn có khả năng thất bại do điều kiện chủng tộc vốn có.
Jack Douglas

27

CẬP NHẬT (2015-08-20):

Bây giờ có một thi chính thức để xử lý upserts thông qua việc sử dụng ON CONFLICT DO UPDATE(tài liệu chính thức). Tại thời điểm viết bài này, tính năng này hiện đang nằm trong PostgreSQL 9.5 Alpha 2, có sẵn để tải xuống tại đây: Thư mục nguồn Postgres .

Đây là một ví dụ, giả sử item_idlà Khóa chính của bạn:

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

Bài gốc ...

Đây là một triển khai tôi đã thực hiện khi mong muốn đạt được khả năng hiển thị về việc liệu có chèn hoặc cập nhật hay không.

Định nghĩa của upsert_datalà hợp nhất các giá trị thành một tài nguyên duy nhất, thay vì phải chỉ định giá và item_id hai lần: Một lần cho bản cập nhật, một lần nữa cho phần chèn.

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

Nếu bạn không thích sử dụng upsert_data, đây là một triển khai thay thế:

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

Nó thực hiện như thế nào?
jb.

1
@jb. không tốt như tôi muốn. Bạn sẽ thấy các hình phạt hiệu suất đáng kể so với thực hiện chèn thẳng. Tuy nhiên, đối với các lô nhỏ hơn (ví dụ 1000 hoặc ít hơn), ví dụ này sẽ hoạt động tốt.
Joshua Burns

0

Điều này sẽ cho bạn biết liệu chèn hoặc cập nhật xảy ra:

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

Nếu cập nhật xảy ra, bạn sẽ nhận được chèn 0, nếu không thì chèn 1 hoặc lỗi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.