web-dev-qa-db-ja.com

PostgresのCONFLICT DO UPDATEに対するINSERTとINSERTまたはUPDATE

3列の_stock_price_alert_テーブルがあります。 _stock_price_id_は_PRIMARY KEY_であり、他のテーブルに対しても_FOREIGN KEY_です。以下のテーブル定義:

_create table stock_price_alert (
    stock_price_id integer references stock_price (id) on delete cascade not null,
    fall_below_alert boolean not null,
    rise_above_alert boolean not null,
    primary key (stock_price_id)
);
_

次のいずれかが必要です。

1)INSERTレコード(存在しない場合)

_-- query 1
INSERT INTO stock_price_alert (stock_price_id, fall_below_alert, rise_above_alert)
VALUES (1, true, false);
_

2)UPDATEレコード(存在する場合)

_-- query 2
UPDATE stock_price_alert SET
    fall_below_alert = true,
    rise_above_alert = false
WHERE stock_price_id = 1;
_

最初に、クエリ(1)または(2)のどちらを実行するかを決定するために、SELECTクエリを_stock_price_alert_テーブルで発行する必要があります。

Postgresは_INSERT INTO TABLE .... ON CONFLICT DO UPDATE ..._をサポートしています:

_-- query 3
INSERT INTO stock_price_alert (stock_price_id, fall_below_alert, rise_above_alert)
VALUES (1, true, false)
ON CONFLICT (stock_price_id) DO UPDATE SET
    fall_below_alert = EXCLUDED.fall_below_alert,
    rise_above_alert = EXCLUDED.rise_above_alert;
_

クエリ(1)または(2)を使用する代わりに、常にクエリ(3)を使用できますか?それから、事前にSELECTクエリを発行する必要がなく、コードを簡素化するのに役立ちます。

しかし、私は疑問に思っています、どれがベストプラクティスですか?クエリ(3)はパフォーマンスの問題や望ましくない副作用を引き起こしますか?ありがとう。

11
Shuwn Yuan Tee

クエリ3は、Postgres 9.5で導入された "UPSERT"(= UPDATEまたはINSERT)のPostgres構文です。

ドキュメント から:

ON CONFLICT DO UPDATEは、アトミックなINSERTまたはUPDATEの結果を保証します。独立したエラーがない限り、これらの2つの結果のいずれかは、同時実行性が高い場合でも保証されます。これは、UPSERT –“ UPDATEまたはINSERT”とも呼ばれます。

これは、達成しようとしているもののベストプラクティスです。

9

ON CONFLICTに加えてWHERE NOT EXISTSを使用すると、INSERTS(まだUPSERTSをテストしていない)の方がはるかに高速であることに気付きました。通常、ON CONFLICTによる存在チェックの処理を許可するよりも約3倍高速です。これはUPSERTSに持ち越され、INSERTを実行してからUPDATEを実行する方が高速になる可能性があると思います。挿入のみのテストです...

    --so i can keep rerunning
    DROP TABLE if exists temp1;
    DROP TABLE if exists temp2;

    --create a billion rows
    SELECT GENERATE_SERIES AS id INTO TEMP temp1
    FROM GENERATE_SERIES(1, 10000000);

    CREATE UNIQUE INDEX ux_id  ON temp1(id);
    ALTER TABLE temp1 CLUSTER ON ux_id;

    --create a second table to insert from, with the same data
    SELECT * INTO TEMP temp2 
    FROM temp1;

    CREATE UNIQUE INDEX ux_id2  ON temp2(id);
    ALTER TABLE temp2 CLUSTER ON ux_id2;

    --test inserting with on conflict only
    INSERT INTO temp1(id)
    SELECT id
    FROM temp2 ON conflict DO nothing;
    --execution time: 14.71s (1million rows)

    --test inserting with not exists and on conflict
    INSERT INTO temp1(id)
    SELECT t2.id
    FROM temp2 t2
    WHERE NOT EXISTS (SELECT 1 FROM temp1 t1 WHERE t2.id = t1.id) 
    ON conflict DO nothing;
    --execution time: 5.78s (1million rows)
1
Jeremy Giaco