相当于 ON CONFLICT DO NOTHING for UPDATE postgres

BHC*_*BHC 17 sql postgresql where-clause sql-update sql-insert

如果更新的版本不会违反主键约束,我想更新我的 postgres 数据库中的行。如果可以,我想保持原样。

假设表有主键col1, col2, col3,如果我运行这样的查询:

UPDATE table SET (col1, col2) = ('A', 'B') 
      WHERE col1='D' AND col2='E';
Run Code Online (Sandbox Code Playgroud)

查询将失败,如果存在两个条目,我将收到重复键错误:

'A', 'B', 'C'
'D', 'E', 'C'
Run Code Online (Sandbox Code Playgroud)

col3在现有行和要更新的行之间是相同的。

如果我正在INSERT使用我会使用的行,ON CONFLICT DO NOTHING但我找不到它的实现UPDATE。是否存在等价物?

GMB*_*GMB 13

您可以使用带有WHERE NOT EXISTS子句的相关子查询来确保您的更新不会生成重复项,例如:

UPDATE mytable t
SET (col1, col2) = ('AAA', 'BBB')
WHERE t.col1 = 'AAB' and t.col2 = 'BBA'
AND NOT EXISTS (
   SELECT 1 FROM mytable WHERE col1 = 'AAA' AND col2 = 'BBB' AND col3 = t.col3
);
Run Code Online (Sandbox Code Playgroud)

这个 db fiddle 中测试。

编辑

正如 Roman Konoval 所评论的,请注意,如果并发事务在UPDATE运行时插入相同的键,这仍然会产生重复键错误。这指出更新表的主键不是一个好习惯(有关此问题的详细讨论,请参阅@Lau 的以下回答)。

  • 任何 unique() 约束都可能发生重复错误,不限于 PK。所以这个评论仍然成立,但我认为这不应该是一种“不好的做法”。如果您想要保证,您可以使用锁,或者仅通过使用较小的批量大小来处理并发性,或者如果可以的话,使用更细粒度的 WHERE 来限制集合。 (2认同)

FXD*_*FXD 11

AFAIK,没有这样的等价物。

假设您正在开发一个连接到 postgresql 数据库的应用程序,在您的问题的上下文中,您需要记住一些事情:

  • 这可能是反直觉的,但你应该考虑由DB被抛出的错误东西。
    这只是获取状态,并不意味着应用程序崩溃。
  • 对于插入,有一个可选的操作选择on conflict(更新或不更新),所以有一个让你决定的语法是有意义的。
    对于更新,您唯一能做的就是……什么也不做。
    那么,既然别无选择,为什么 SQL 会让你要求做一些特定的事情呢?请记住,DB 报告错误是好的,因此让 DB 什么都不做并告诉您原因。
  • 最后,更新主键是一种不好的做法。
    ON CONFLICT ...插入片段并不旨在以更新主键字段。事实上恰恰相反:它旨在更新单个记录中除主键之外的所有字段。

当我在这一点上时,请注意主键上的冲突没有必要使查询失败
1 条记录与“方便”ON UPDATE NO ACTION外键也会使其失败(这仍然比更新 1000 万条记录更好50 桌与ON UPDATE CASCADE...)。顺便说一句,你知道 Oracle 甚至没有这个ON UPDATE CASCADE条款吗?你认为这是什么原因?


在那种情况下你能/不应该做什么?

  1. 不要像我说的那样更新主键。您的问题对于UNIQUE约束仍然有效,但请不要更新主键。
  2. 不要试图查看是否已存在冲突记录。它可能需要很长时间并且仍然不可靠。
    你真的想选择数百万条记录只是为了避免错误代码吗?
    此外,当您扩展到其他约束(CHECKEXCLUSION)时,您是否真的会输入它所需要的附加代码而不会出错,以便再次避免错误代码?
    最后,如果您实现了行级安全,冲突可能来自您看不到的记录。
  3. 处理您的应用程序中的错误代码。接收状态良好
  4. 如果您正在进行交易,请使用保存点
    这是 DB 错误唯一令人讨厌的事情:如果您在交易过程中遇到一个错误,您将开始current transaction is aborted, commands ignored until end of transaction block为所有事情而烦恼。
    希望您不需要回滚整个事务并从头开始重做所有事情。您可以使用以下代码段逃脱。

干得好:

BEGIN;
SAVEPOINT MySavepoint;
UPDATE mytable set myuniquefield = 3; /*2+ records are going to be updated */
rollback to savepoint MySavepoint;
/*Insert Some more queries here*/
COMMIT;
Run Code Online (Sandbox Code Playgroud)

  • 听起来好像您不应该使用酒店名称作为主键……如果竞争对手在街对面开设了一家与第一家酒店品牌相同的酒店怎么办?IMO,您应该将其引用为税务登记号或其他不会更改的内容,除非是公司变更(在这种情况下,这将是一个新记录) (4认同)