web-dev-qa-db-ja.com

アップサートがPostgreSQL9.5 + UPSERTのアップデートであったかどうかを確認するにはどうすればよいですか?

書き込み可能なCTEは、 PostgreSQLでの重複更新時に挿入? で説明されているように、9.5より前のUPSERTのソリューションと見なされていました。

次の書き込み可能なCTEイディオムを使用して、最終的にUPDATEまたはINSERTのどちらであるかを示す情報を使用してUPSERTを実行することができます。

WITH
    update_cte AS (
        UPDATE t SET v = $1 WHERE id = $2 RETURNING 'updated'::text status
    ),
    insert_cte AS (
        INSERT INTO t(id, v) SELECT $2, $1 WHERE NOT EXISTS
            (SELECT 1 FROM update_cte) RETURNING 'inserted'::text status
    )
 (SELECT status FROM update_cte) UNION (SELECT status FROM insert_cte)

このクエリは「更新済み」または「挿入済み」のいずれかを返すか、 https://dba.stackexchange.com/questions/78510/why-is-cte)で説明されているように、制約違反で失敗する可能性があります(まれに) -open-to-lost-updates

PostgreSQL 9.5以降の新しい「UPSERT」構文を使用して同様のことを実現できますか?その最適化の恩恵を受け、制約違反の可能性を回避できますか?

22
Paul Guyot

私は信じている xmax::text::int > 0が最も簡単なトリックです:

so=# DROP TABLE IF EXISTS tab;
NOTICE:  table "tab" does not exist, skipping
DROP TABLE
so=# CREATE TABLE tab(id INT PRIMARY KEY, col text);
CREATE TABLE
so=# INSERT INTO tab(id, col) VALUES (1,'a'), (2, 'b');
INSERT 0 2
so=# INSERT INTO tab(id, col)
VALUES (3, 'c'), (4, 'd'), (1,'aaaa')
ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col
returning *,case when xmax::text::int > 0 then 'updated' else 'inserted' end,ctid;
 id | col  |   case   | ctid
----+------+----------+-------
  3 | c    | inserted | (0,3)
  4 | d    | inserted | (0,4)
  1 | aaaa | updated  | (0,5)
(3 rows)

INSERT 0 3
so=# INSERT INTO tab(id, col)
VALUES (3, 'c'), (4, 'd'), (1,'aaaa')
ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col
returning *,case when xmax::text::int > 0 then 'updated' else 'inserted' end,ctid;
 id | col  |  case   | ctid
----+------+---------+-------
  3 | c    | updated | (0,6)
  4 | d    | updated | (0,7)
  1 | aaaa | updated | (0,8)
(3 rows)

INSERT 0 3
15
Vao Tsun

@ lad2025の回答 から描画すると、WHEREで 設定 および カスタマイズされたオプション関連関数 で悪用することで結果を得ることができます。必要な副作用を得るための条項。

_CREATE TABLE t(id INT PRIMARY KEY, v TEXT);

INSERT INTO t (id, v)
    SELECT $1, $2
    WHERE 'inserted' = set_config('upsert.action', 'inserted', true)
    ON CONFLICT (id) DO UPDATE
        SET v = EXCLUDED.v
        WHERE 'updated' = set_config('upsert.action', 'updated', true)
RETURNING current_setting('upsert.action') AS "upsert.action";
_

_set_config_の3番目のパラメーターは_is_local_です。trueは、トランザクションの終了時に設定がなくなることを意味します。より正確には、current_setting('upsert.action')はセッションが終了するまでNULLを返します(エラーをスローしません)。

7
Paul Guyot

SQL ServerMERGEステートメントには、文字列$actionを返す'INSERT', 'UPDATE', or 'DELETE'があります。

Postgresqlの場合、RETURNINGと同様のことを行う関数/変数が見つかりません。

これを回避する1つの方法は、列is_updatedをテーブルに追加することです。

DROP TABLE IF EXISTS tab;

CREATE TABLE tab(id INT PRIMARY KEY, col VARCHAR(100),
                 is_updated BOOLEAN DEFAULT false);
INSERT INTO tab(id, col) VALUES (1,'a'), (2, 'b');


-- main query
INSERT INTO tab(id, col)
VALUES (3, 'c'), (4, 'd'), (1,'aaaa')
ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col, is_updated = true
RETURNING id,col,
          CASE WHEN is_updated THEN 'UPDATED' ELSE 'INSERTED' END AS action;

Rextester Demo

出力:

╔════╦══════╦══════════╗
║ id ║ col  ║  action  ║
╠════╬══════╬══════════╣
║  3 ║ c    ║ INSERTED ║
║  4 ║ d    ║ INSERTED ║
║  1 ║ aaaa ║ UPDATED  ║
╚════╩══════╩══════════╝
5
Lukasz Szozda

(xmax::text::bigint > 0)または(NOT xmax = 0)。トランザクションカウントが整数オーバーフローに達すると、整数へのタイプキャストは中断されます。

2
Misha