问题描述
在 Postgresql 中,当插入带有 on conflict
和 returning
子句的行时,返回什么?插入的行?插入前的行?如果没有插入行怎么办?
考虑以下使用 ... on conflict do nothing returning ...
的语句:
insert into test
values (:id,:name)
on conflict do nothing
returning id;
以及以下语句 ... on conflict (...) do update ... returning ...
:
insert into test
values (:id,:name)
on conflict (name) do update
set id = EXCLUDED.id + 10
returning id;
并假设 id
和 name
列都是唯一的。以上语句将返回什么?
密切相关,如何找到导致冲突的现有行的 ID?
PS:是的,我可以轻松地尝试一下。事实上,这就是我所做的(见答案)。很容易做出错误的猜测,很难记住答案,而且重现实验的成本很高,所以我认为这个问题对其他人有用。
解决方法
returning
子句总是返回写入到表中的信息(即插入或更新)。如果没有发生写入,则不会返回任何行。
因此,如果您想获取导致冲突的现有行的 id,则不能使用 on conflict do nothing
。相反,您可以使用带有虚拟更新的 on conflict (...) do update
来引发实际上不修改现有行内容的写入,如下所示:
insert into test
values (4,'singleton')
on conflict (name) do update
set id = test.id -- force a write,but don't change anything
returning id;
但是请注意,这种人为写入可能仍然会产生不良副作用,尤其是在触发器到位的情况下。即使数据没有改变,您也会生成新的元组。
实验证明:
drop table if exists test;
create table test (
id int primary key,name text unique
);
insert into test
values (1,'singleton');
select * from test;
-- +--+---------+
-- |id|name |
-- +--+---------+
-- |1 |singleton|
-- +--+---------+
insert into test
values (2,'singleton')
on conflict do nothing
returning id;
-- result: empty
insert into test
values (2,'something else')
on conflict (name) do update
set id = EXCLUDED.id + 10
returning id;
-- result:
-- +--+
-- |id|
-- +--+
-- |2 |
-- +--+
insert into test
values (3,'singleton')
on conflict (name) do update
set id = EXCLUDED.id + 10
returning id;
-- result:
-- +--+
-- |id|
-- +--+
-- |13|
-- +--+
insert into test
values (4,'singleton')
on conflict (name) do update
set id = test.id
returning id;
-- result:
-- +--+
-- |id|
-- +--+
-- |13|
-- +--+
select * from test;
-- result:
-- +--+--------------+
-- |id|name |
-- +--+--------------+
-- |2 |something else|
-- |13|singleton |
-- +--+--------------+