从条件 INSERT 中获取 Id

Get Id from a conditional INSERT

对于像这样的 table:

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

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

给定一个用户 name,插入一条新记录和 return 新的 id。但是,如果 name 已经存在,则 return id.

我知道 PostgreSQL 9.5 中用于 ON CONFLICT(column) DO UPDATE/NOTHING 的新语法,但我不知道它有什么帮助,如果有的话,考虑到我需要 id被 return 编辑。

看来RETURNING idON CONFLICT不属于一起

对于单行插入且无更新:

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

The manual 关于主查询和 with 子查询部分:

The primary query and the WITH queries are all (notionally) executed at the same time

虽然这听起来对我来说 "same snapshot" 我不确定,因为我不知道名义上 在那种情况下意味着什么。

但是there is also:

The sub-statements in WITH are executed concurrently with each other and with the main query. Therefore, when using data-modifying statements in WITH, the order in which the specified updates actually happen is unpredictable. All the statements are executed with the same snapshot

如果我理解正确的话 same snapshot 位可以防止竞争条件。但是我再次不确定 all the statements 它是否仅指 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

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

这是手册中解释您的情况的关键语句:

The syntax of the RETURNING list is identical to that of the output list of SELECT. Only rows that were successfully inserted or updated will be returned. For example, if a row was locked but not updated because an ON CONFLICT DO UPDATE ... WHERE clause condition was not satisfied, the row will not be returned.

大胆强调我的。

对于单行插入:

同一个 table

上没有并发写入负载
WITH ins AS (
   INSERT INTO users(name)
   VALUES ('new_usr_name')         -- input value
   ON     CONFLICT(name) DO NOTHING
   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;

table

上可能存在并发写入负载

考虑一下(对于 单行 INSERT):

  • Is SELECT or INSERT in a function prone to race conditions?

插入行集:

三个都有非常详细的解释。