UPSERT
PostgreSQLのさまざまな実装について読んだことがありますが、これらのソリューションはすべて比較的古く、または比較的エキゾチックです(たとえば、書き込み可能なCTEを使用)。
そして、これらのソリューションが推奨されているため古くなっているのか、それとも(ほとんどすべてがそうである)実稼働での使用に適さない単なるおもちゃの例であるのかをすぐに調べるのは、psqlの専門家ではありません。
PostgreSQLでUPSERTを実装する最もスレッドセーフな方法は何ですか?
UPSERT
PostgreSQLのさまざまな実装について読んだことがありますが、これらのソリューションはすべて比較的古く、または比較的エキゾチックです(たとえば、書き込み可能なCTEを使用)。
そして、これらのソリューションが推奨されているため古くなっているのか、それとも(ほとんどすべてがそうである)実稼働での使用に適さない単なるおもちゃの例であるのかをすぐに調べるのは、psqlの専門家ではありません。
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');
更新(2015-08-20):
ON CONFLICT DO UPDATE
(公式ドキュメント)を使用してアップサートを処理するための公式の実装があります。この記事の執筆時点では、この機能は現在PostgreSQL 9.5 Alpha 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を2回指定するのではなく、値を単一のリソースに統合することです。更新のために1回、挿入のために1回。
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
これにより、挿入または更新が発生したかどうかがわかります。
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またはエラーが挿入されます。