tag
(uuid)とid
(テキスト)の2つの列を持つテーブルname
があります。テーブルに新しいタグを挿入したいのですが、タグが既に存在する場合は、既存のレコードのid
を取得するだけです。
ON CONFLICT DO NOTHING
をRETURNING "id"
と組み合わせて使用できると想定しました。
INSERT INTO
"tag" ("name")
VALUES( 'foo' )
ON CONFLICT DO NOTHING
RETURNING "id";
ただし、「foo」という名前のタグがすでに存在する場合、これは空の結果セットを返します。
次に、noop DO UPDATE
句を使用するようにクエリを変更しました。
INSERT INTO
"tag" ("name")
VALUES( 'foo' )
ON CONFLICT ("name") DO UPDATE SET "name" = 'foo'
RETURNING "id";
これは意図したとおりに機能しますが、名前を既存の値に設定しているだけなので、やや混乱します。
これはこの問題に対処する方法ですか、それとも私が見逃しているより簡単な方法がありますか?
これは、(私がテストした限り)3つのケースすべてで機能します。挿入される値がすべて新しいか、すべてすでにテーブルにあるか、または混在している場合:
WITH
val (name) AS
( VALUES -- rows to be inserted
('foo'),
('bar'),
('zzz')
),
ins AS
( INSERT INTO
tag (name)
SELECT name FROM val
ON CONFLICT (name) DO NOTHING
RETURNING id, name -- only the inserted ones
)
SELECT COALESCE(ins.id, tag.id) AS id,
val.name
FROM val
LEFT JOIN ins ON ins.name = val.name
LEFT JOIN tag ON tag.name = val.name ;
新しいON CONFLICT
構文。
これがどのように機能するかはわかりませんが、試してみる別のオプションとして、ここでは同じことを昔ながらの方法で行っています(ON CONFLICT
なし):
WITH items (name) AS (VALUES ('foo'), ('bar'), ('zzz')),
added AS
(
INSERT INTO tag (name)
SELECT name FROM items
EXCEPT
SELECT name FROM tag
RETURNING id
)
SELECT id FROM added
UNION ALL
SELECT id FROM tag
WHERE name IN (SELECT name FROM items)
;
つまり、tag
テーブルにない[一意の]名のみを挿入して、IDを返します。これを、tag
に存在する名前のIDと組み合わせて、最終的な出力にします。また、name
を ypercubeᵀᴹで推奨 のように出力にスローして、どのIDがどの名前と一致するかを確認することもできます。