web-dev-qa-db-ja.com

アップサートで競合する行のIDを取得するにはどうすればよいですか?

tag(uuid)とid(テキスト)の2つの列を持つテーブルnameがあります。テーブルに新しいタグを挿入したいのですが、タグが既に存在する場合は、既存のレコードのidを取得するだけです。

ON CONFLICT DO NOTHINGRETURNING "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";

これは意図したとおりに機能しますが、名前を既存の値に設定しているだけなので、やや混乱します。

これはこの問題に対処する方法ですか、それとも私が見逃しているより簡単な方法がありますか?

18
Der Hochstapler

これは、(私がテストした限り)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構文。

9
ypercubeᵀᴹ

これがどのように機能するかはわかりませんが、試してみる別のオプションとして、ここでは同じことを昔ながらの方法で行っています(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と組み合わせて、最終的な出力にします。また、nameypercubeᵀᴹで推奨 のように出力にスローして、どのIDがどの名前と一致するかを確認することもできます。

4
Andriy M