postgresql 上的冲突 - 不能再次影响行

Ale*_*dra 11 postgresql

我有一张桌子,我在 data_id 上有自动编号/序列

tabledata
---------
data_id   [PK]
data_code [Unique]
data_desc
Run Code Online (Sandbox Code Playgroud)

示例代码:

insert into tabledata(data_code,data_desc) values(Z01,'red')
on conflict (data_code) do update set data_desc=excluded.data_desc
Run Code Online (Sandbox Code Playgroud)

工作正常,然后我再次插入

insert into tabledata(data_code,data_desc) values(Z01,'blue')
on conflict (data_code) do update set data_desc=excluded.data_desc
Run Code Online (Sandbox Code Playgroud)

我收到这个错误

[Err] 错误:ON CONFLICT DO UPDATE 命令不能再次影响行 提示:确保建议在同一命令中插入的行没有重复的约束值。

这是我的真实代码

insert into psa_aso_branch(branch_code,branch_desc,regional_code,status,created_date,lastmodified_date) 
    (select branch_code, branch, kode_regional, 
    case when status_data='Y' then true 
    else false end, current_date, current_date 
    from branch_history) on conflict (branch_code) do
    update set branch_desc = excluded.branch_desc, regional_code = excluded.regional_code,status = (case when excluded.status='Y' then true else false end), created_date=current_date, lastmodified_date=current_date;
Run Code Online (Sandbox Code Playgroud)

第一个工作正常,但不是下一个(就像我之前给你的例子)

Yak*_*idi 15

我已经被这个问题困扰了大约24小时。

当我在 cli 上测试查询时,它工作正常,这很奇怪。当我使用一个数据行进行插入时,它工作正常。此错误仅在我使用时出现insert-select

这主要不是因为insert-select问题。这是因为select行不是唯一的。这将CONFLICT多次触发。

感谢@zivaricha 评论。我根据他的笔记进行实验。只是一开始很难理解。

解决方案:使用distinct来确保select返回唯一的结果。

  • 在“ON CONFLICT”语句之前添加“LIMIT 1”,一切正常 (4认同)
  • 我也希望选择一种不同的方法能够发挥作用,但我仍然看到同样的错误。有什么想法吗? (2认同)

Sma*_*ror 9

我们可以从源代码中找到错误信息,我们可以简单地理解为什么会出现这样的错误ON CONFLICT DO UPDATE command cannot affect row a second time

在PostgreSQL的源码src/backend/executor/nodeModifyTable.c和函数中ExecOnConflictUpdate(),我们可以找到这样的注释:

当在同一命令中再次更新刚刚插入的元组时,可能会发生这种情况。例如,因为插入了具有相同冲突键值的多行。
这有点类似于 ExecUpdate() TM_SelfModified 情况。我们不想继续,因为这会导致同一行以某种未指定的顺序第二次更新,并且与普通的 UPDATE 相比,没有需要破坏的历史行为。

正如评论所说,我们无法更新要插入的行INSERT ... ON CONFLICT,就像:

postgres=#  CREATE TABLE t (id int primary key, name varchar);

postgres=#  INSERT INTO t VALUES (1, 'smart'), (1, 'keyerror') 
postgres=#  ON CONFLICT (id) DO UPDATE SET name = 'Buuuuuz';
ERROR:  ON CONFLICT DO UPDATE command cannot affect row a second time
HINT:  Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
Run Code Online (Sandbox Code Playgroud)

记住,postgresql的执行器是一个火山模型,所以它会一一处理我们插入的数据。当我们处理到(1,'smart')时,由于表是空的,所以可以正常插入。当我们到(1, 'keyerror')时,和我们刚刚插入的(1, 'smart')有冲突,所以执行更新逻辑,导致更新了我们自己插入的数据,而PostgreSQL不会让我们做。

同样,我们不能两次更新同一行数据:

postgres=# DROP TABLE IF EXISTS t;
postgres=# CREATE TABLE t (id int primary key, name varchar);

postgres=# INSERT INTO t VALUES (1, 'keyerror'), (1, 'buuuuz') 
postgres=# ON CONFLICT (id) DO UPDATE SET name = 'Buuuuuuuuuz';
ERROR:  ON CONFLICT DO UPDATE command cannot affect row a second time
HINT:  Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
Run Code Online (Sandbox Code Playgroud)

  • 您的第二个示例与第一个示例相同,您的意思是先插入 ID 为 1 的行吗? (4认同)

har*_*ddy 6

您可以在现有记录/行上使用更新,而不是在您插入的行上使用。这里 update inon conflict子句适用于排除表中的行。暂时保留行

在第一种情况下插入记录,因为 data_code 没有冲突。并且根本不执行更新

在第二个插入中,您插入的 Z01 已作为 data_code 插入,并且 data_code 是唯一的。

被排除的表在更新后仍保留 data_code 的重复值。所以没有插入记录。在update setdata_code 中必须更改才能正确插入记录

  • @AlexanderChandra 刚刚遇到了同样的问题。只是为了澄清为什么你会收到错误:如果你的数据库中有一条现有的记录,其唯一的 id 123,那么 - 你尝试插入一条 id 123 的新记录 - >“冲突子句”将起作用。但是,如果在同一个插入中尝试插入具有相同唯一 ID (123) 的 2 条记录,则“冲突”子句将无法解决此类情况,并且会引发您遇到的异常。 (4认同)

小智 5

当重复在单个插入中多次发生时,就会出现此错误,
例如您有列 a 、 b 、 c ,并且 a 和 b 的组合是唯一的,并且在重复时您正在更新 c 。现在假设您已经有 a = 1 、 b = 2 、 c = 3 并且您正在插入 a = 1 b = 2 c = 4 和 a = 1 b = 2 c = 4
所以意味着冲突发生两次,因此它无法更新一行两次