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


18

我有一个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";

但是,如果名称“ foo”的标记已存在,则返回空结果集。

然后,我将查询更改为使用noop DO UPDATE子句:

INSERT INTO
    "tag" ("name")
VALUES( 'foo' )
ON CONFLICT ("name") DO UPDATE SET "name" = 'foo'
RETURNING "id";

这可以按预期工作,但是有些混乱,因为我只是将名称设置为已经存在的值。

这是解决此问题的方法,还是我缺少一种更简单的方法?


你尝试了returning excluded.id吗?
a_horse_with_no_name

@a_horse_with_no_name ERROR: missing FROM-clause entry for table "excluded"使用时才给我DO NOTHING
Der Hochstapler '16

Answers:


8

如果要插入的值都是新的,或者表中或混合表中已经全部有,这在所有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语法。


4

不知道它是否会执行,只是作为另一个尝试,这是一种古老的方法(不带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;将其与确实存在的名称的ID相结合tag,以得到最终输出。您还可以name按照ypercubeᵀᴹ的建议将输出放入,以便知道哪个ID与哪个名称匹配。


1
是。我的代码的最后选择也可以写成SELECT .. FROM ins UNION ALL SELECT ... FROM val JOIN tag ... ;
ypercubeᵀᴹ
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.