2016-01-13 6 views
15

Insert, on duplicate update in PostgreSQL?UpsertがPostgreSQL 9.5+ UPSERTのアップデートであったかどうかを調べるには?

に記載したように書き込み可能のCTEは、次の書き込み可能なCTEをイディオムとUPDATEまたはINSERTとして終わったかどうかの情報でUPSERTを行うことが可能となる前に9.5に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」を使用して似たような達成することができますいずれかを返します。構文、benefiその最適化から来て、可能性のある制約違反を避けていますか?

答えて

6

@lad2025's answerから描画、結果を達成することができます乱用すると、settingscustomized options wi th related functionsを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を返します(エラーをスローしません)。

+0

素敵なトリック**** – lad2025

+5

ああ、それは醜いです!それは私のビジョンをぼやけさせる。 〜 – Patrick

+0

あなたは 'xmax :: text :: int> 0'でトランザクションレベルの設定なしでこれを行うことができます(これは本当にオリジナルのハックです) –

4

SQL ServerMERGE文の文字列は、'INSERT', 'UPDATE', or 'DELETE'を返す$actionです。

PostgresqlRETURNINGの類似したことをする関数/変数が見つかりません。

それを回避する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 ║ 
╚════╩══════╩══════════╝ 
+0

出力のVALUES(2、B '')はありますか? – kometen

+0

@kometenそれは必要ありません。それは前に存在する。なぜあなたはテーブル全体を返すのですか?挿入/更新されたレコードだけが – lad2025

+0

But(2、 'b')も挿入されます。 is_updatedのデフォルトはfalseです。だから私はINSERTEDと表示されると思った。 – kometen

1

私は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 
+0

あなたはちょうど使用できます: '' ' 返される(xmax = 0)AS ' '' –

関連する問題