一意の制約を持つテーブルから行を取得する必要が非常に頻繁にある状況があります。行が存在しない場合は、作成して戻ります。たとえば、私のテーブルは次のようになります。
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;
ちょっと面倒そうです。私の質問は次のとおりだと思います。
(私の)望ましい行動を許可しない理由は何ですか?
これを行うためのよりエレガントな方法はありますか?
これは、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;