【问题标题】:Equivalent of ON CONFLICT DO NOTHING for UPDATE postgres等效于 ON CONFLICT DO NOTHING for UPDATE postgres
【发布时间】:2019-01-11 15:15:55
【问题描述】:

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

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

UPDATE table SET (col1, col2) = ('A', 'B') 
      WHERE col1='D' AND col2='E';

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

'A', 'B', 'C'
'D', 'E', 'C'

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

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

【问题讨论】:

  • 如果你知道它存在,你可以简单地把它变成一个 INSERT

标签: sql postgresql sql-update subquery sql-insert


【解决方案1】:

您可以使用带有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
);

this db fiddle 测试。

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

【讨论】:

  • 当一个事务将key更新为不退出但并发事务插入key的值时,仍然会产生重复key错误
  • @RomanKonoval:正确。我编辑了我的帖子以包含这些重要信息。谢谢!
  • 任何 unique() 约束都可能发生重复错误,它不限于 PK。所以评论仍然成立,但我认为这不应该是一种“坏习惯”。如果您想要保证,您可以使用锁或仅通过使用较小的批处理大小来处理并发,或者如果可以的话,使用更细粒度的 WHERE 来限制集合。
【解决方案2】:

AFAIK,没有这样的等价物。

假设您正在开发一个连接到 postgresql 数据库的应用程序,根据您的问题,您需要牢记以下几点:

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

当我谈到这一点时,请注意,查询失败不需要主键冲突
具有“方便”ON UPDATE NO ACTION 外键的 1 条记录也会使其失败(这仍然比使用 ON UPDATE CASCADE 更新 50 个表中的 10M+ 记录要好...)。顺便说一句,您知道 Oracle 甚至没有 ON UPDATE CASCADE 子句吗?您认为这是什么原因?


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

  1. 不要更新主键,就像我说的那样。您的问题对于 UNIQUE 约束仍然有效,但请不要更新主键。
  2. 不要尝试查看是否存在冲突记录。这可能需要很长时间,但仍然不可靠。
    您真的要选择数百万条记录来避免错误代码吗?
    此外,当您扩展到其他约束(CHECKEXCLUSION)时,您是否真的会键入没有错误的附加代码,以便再次避免错误代码?
    最后,如果您实施了行级安全性,冲突可能来自您看不到的记录。
  3. 处理应用中的错误代码。接收状态为良好
  4. 如果您正在进行交易,请使用保存点
    这是数据库错误唯一令人讨厌的事情:如果您在事务中间遇到一个,您将开始收到 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;

【讨论】:

  • 这个答案的后半部分很好。在更新之前检查是否存在在我拥​​有数十亿行的数据库上会花费太多时间。为了解决我的问题,我更新了一个没有主键的表,然后将其插入(使用 ON CONFLICT)到一个具有主键的新表中。这样我就不用担心处理错误代码了,但是如果你不能这样做,你的建议很好。
  • 从不更新主键?您是否还建议不要使用自然主键?我的意思是,如果酒店名称发生变化怎么办?
  • 这听起来好像您不应该使用酒店的名称作为主键......如果竞争对手在街对面开了一家与最初的品牌相同的酒店怎么办? IMO,您应该将其引用到税务登记号或不会改变的东西,除非它是公司变更(在这种情况下,这将是一个新记录)
猜你喜欢
  • 2021-12-24
  • 1970-01-01
  • 2018-08-02
  • 2018-08-02
  • 1970-01-01
  • 2018-09-10
  • 2020-12-22
  • 2023-03-18
  • 2016-05-11
相关资源
最近更新 更多