web-dev-qa-db-ja.com

更新せずに、ONCONFLICTを使用してINSERTから行を返す

一意の制約を持つテーブルから行を取得する必要が非常に頻繁にある状況があります。行が存在しない場合は、作成して戻ります。たとえば、私のテーブルは次のようになります。

CREATE TABLE names(
    id SERIAL PRIMARY KEY,
    name TEXT,
    CONSTRAINT names_name_key UNIQUE (name)
);

そしてそれは含まれています:

id | name
 1 | bob 
 2 | alice

それから私はしたい:

 INSERT INTO names(name) VALUES ('bob')
 ON CONFLICT DO NOTHING RETURNING id;

多分:

 INSERT INTO names(name) VALUES ('bob')
 ON CONFLICT (name) DO NOTHING RETURNING id

ボブのIDを返すようにします1。ただし、RETURNINGは、挿入された行または更新された行のみを返します。したがって、上記の例では、何も返されません。希望どおりに機能させるには、実際に次のことを行う必要があります。

INSERT INTO names(name) VALUES ('bob') 
ON CONFLICT ON CONSTRAINT names_name_key DO UPDATE
SET name = 'bob'
RETURNING id;

ちょっと面倒そうです。私の質問は次のとおりだと思います。

  1. (私の)望ましい行動を許可しない理由は何ですか?

  2. これを行うためのよりエレガントな方法はありますか?

16
ira

これは、UPSERTに関連する(ただし異なる)SELECT or INSERTの繰り返し発生する問題です。 。 Postgres9.5の新しいUPSERT機能は引き続き機能します。

WITH ins AS (
   INSERT INTO names(name)
   VALUES ('bob')
   ON     CONFLICT ON CONSTRAINT names_name_key DO UPDATE
   SET    name = NULL
   WHERE  FALSE      -- never executed, but locks the row
   RETURNING id
   )
SELECT id FROM ins
UNION  ALL
SELECT id FROM names
WHERE  name = 'bob'  -- only executed if no INSERT
LIMIT  1;

このようにして、実際には必要なしに新しい行バージョンを作成することはありません。

Postgresでは、UPDATEがと同じ値に設定されている場合でも、 MVCCモデル -のためにすべてのnameが新しいバージョンの行を書き込むことを知っていると思います。前。これにより、操作のコストが高くなり、同時実行の問題が発生したり、特定の状況で競合がロックされたりして、テーブルがさらに肥大化します。

詳細な説明とこれを関数にラップする方法:

「除外された」行がRETURNING句に含まれないのはなぜですか?

同時のUPDATEまたはDELETE(別のセッションから)が不可能な場合は、行をロックする必要がなく、単純化できます。

WITH ins AS (
   INSERT INTO names(name)
   VALUES ('bob')
   ON     CONFLICT ON CONSTRAINT names_name_key DO NOTHING  -- no lock needed
   RETURNING id
   )
SELECT id FROM ins
UNION  ALL
SELECT id FROM names
WHERE  name = 'bob'  -- only executed if no INSERT
LIMIT  1;
17