【问题标题】:Return rows from INSERT with ON CONFLICT without needing to update使用 ON CONFLICT 从 INSERT 返回行,无需更新
【发布时间】:2017-03-12 10:49:43
【问题描述】:

我有一种情况,我经常需要从具有唯一约束的表中获取一行,如果不存在,则创建它并返回。 例如我的表可能是:

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

并让它返回 bob 的 ID 1。但是,RETURNING 仅返回插入或更新的行。所以,在上面的例子中,它不会返回任何东西。为了让它按需要运行,我实际上需要:

INSERT INTO names(name) VALUES ('bob') 
ON CONFLICT ON CONSTRAINT names_name_key DO UPDATE
SET name = 'bob'
RETURNING id;

这似乎有点麻烦。我想我的问题是:

  1. 不允许(我的)期望行为的原因是什么?

  2. 有没有更优雅的方法来做到这一点?

【问题讨论】:

    标签: sql postgresql upsert postgresql-9.5


    【解决方案1】:

    这是 SELECT or INSERT 反复出现的问题,与 UPSERT 相关(但不同于)。 Postgres 9.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 model - 即使 name 设置为与以前相同的值。这会使操作变得更加昂贵,在某些情况下增加可能的并发问题/锁争用,并且还会使表膨胀。

    然而,仍然有一个比赛条件的小角落。并发事务可能添加了一个冲突行,该行在同一语句中尚不可见。然后INSERT SELECT 空出来。

    单行UPSERT的正确解决方案:

    批量 UPSERT 的一般解决方案:

    没有并发写入负载

    如果并发写入(来自不同会话)是不可能的,您不需要锁定行并且可以简化:

    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;
    

    【讨论】:

    • 感谢您的回答!我认为这似乎是一种“更好”的方法,但我不确定与我描述的方法有什么实际区别?
    • @ira:我在上面添加了更多解释。
    • @ErwinBrandstetter -- 这总是返回一个 id 吗?我试过了,但似乎没有用 -- stackoverflow.com/q/46586793/435563 -- 也许我做错了什么?
    • Ok ... 所以上面的错误是这样的,我认为:如果name 已经是nullSET name = NULL where false 将不会获得锁定。
    • 不是这样的,混淆已经在stackoverflow.com/q/46586793/435563整理出来了。
    猜你喜欢
    • 2017-04-02
    • 2023-02-04
    • 2020-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-02
    • 1970-01-01
    相关资源
    最近更新 更多