如何在 upsert 中获取冲突行的 ID?

Der*_*ler 22 postgresql upsert postgresql-9.5

我有一个tag包含 2 列的表:id(uuid) 和name(text)。我现在想在表中插入一个新标签,但如果该标签已经存在,我只想获取id现有记录的 。

我以为我可以ON CONFLICT DO NOTHING结合使用RETURNING "id"

INSERT INTO
    "tag" ("name")
VALUES( 'foo' )
ON CONFLICT DO NOTHING
RETURNING "id";
Run Code Online (Sandbox Code Playgroud)

但是,如果名称为“foo”的标签已经存在,则会返回一个空的结果集。

然后我将查询更改为使用 noopDO UPDATE子句:

INSERT INTO
    "tag" ("name")
VALUES( 'foo' )
ON CONFLICT ("name") DO UPDATE SET "name" = 'foo'
RETURNING "id";
Run Code Online (Sandbox Code Playgroud)

这按预期工作,但有点令人困惑,因为我只是将名称设置为已经存在的值。

这是解决这个问题的方法还是我缺少更简单的方法?

ype*_*eᵀᴹ 12

这将在所有 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 ;
Run Code Online (Sandbox Code Playgroud)

可能还有其他一些方法可以做到这一点,也许不使用新ON CONFLICT语法。