从条件INSERT获取ID

时间:2016-03-18 11:53:02

标签: sql postgresql concurrency upsert

对于像这样的表:

CREATE TABLE Users(
    id SERIAL PRIMARY KEY,
    name TEXT UNIQUE
);

以下操作的正确单一查询插入是什么:

给定用户name,插入新记录并返回新的id。但是,如果name已经存在,只需返回id

我知道PostgreSQL 9.5中ON CONFLICT(column) DO UPDATE/NOTHING的新语法,但我无法弄清楚,如果有的话,它可以提供帮助,因为我需要返回id

似乎RETURNING idON CONFLICT不属于一起。

2 个答案:

答案 0 :(得分:8)

UPSERT实现非常复杂,可以安全地防止并发写访问。在初始开发期间查看用作日志的this Postgres Wiki。 Postgres黑客决定不在Postgres 9.5的第一个版本的RETURNING子句中包含“排除”行。他们可能会为下一个版本构建一些东西。

这是手册中解释您情况的重要声明:

  

RETURNING列表的语法与输出的语法相同   SELECT列表。 仅限已成功插入或更新的行   将被退回。例如,如果一行被锁定但未更新   因为ON CONFLICT DO UPDATE ... WHERE条款不是   满意的,行不会退回。

大胆强调我的。

单行 插入:

WITH ins AS (
   INSERT INTO users(name)
   VALUES ('new_usr_name')         -- input value
   ON     CONFLICT(name) DO UPDATE
   SET    name = name WHERE FALSE  -- never executed, just to lock row
   RETURNING users.id
   )
SELECT id FROM ins
UNION  ALL
SELECT id FROM users          -- 2nd SELECT never executed if INSERT successful
WHERE  name = 'new_usr_name'  -- input value a 2nd time
LIMIT  1;

或者包装成一个函数,只提供一次新的名称​​ 。就像在这里演示的那样(也考虑LIMIT 1的解释):

可能的竞争:并发事务可能会更改/删除INSERT尝试与SELECT之间的现有行。极不可能,但可能。

如果您没有(可能的)并发写访问权(或者只是不关心),请简化:

...
ON     CONFLICT(name) DO NOTHING
...

要插入

答案 1 :(得分:2)

对于单行插入而不进行更新:

with i as (
    insert into users (name)
    select 'the name'
    where not exists (
        select 1
        from users
        where name = 'the name'
    )
    returning id
)
select id
from users
where name = 'the name'

union all

select id from i
关于主要和with子查询部分的

The manual

  

主查询和WITH查询同时执行(名义

虽然这对我来说是“同样的快照”,但我不确定,因为我不知道 notionalally 在这种情况下的含义。

但是there is also

  

WITH中的子语句彼此同时并与主查询一起执行。因此,在WITH中使用数据修改语句时,指定更新实际发生的顺序是不可预测的。 所有语句都使用相同的快照执行

如果我理解正确相同的快照位可以防止竞争条件。但同样我不确定所有语句是否只引用with子查询中除主查询之外的语句。为避免任何疑问,请将上一个查询中的select移动到with子查询:

with s as (
    select id
    from users
    where name = 'the name'
), i as (
    insert into users (name)
    select 'the name'
    where not exists (select 1 from s)
    returning id
)
select id from s
union all
select id from i