对于像这样的表:
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 id
和ON CONFLICT
不属于一起。
答案 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
子查询部分的
主查询和WITH查询同时执行(名义)
虽然这对我来说是“同样的快照”,但我不确定,因为我不知道 notionalally 在这种情况下的含义。
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