如何更新表中的行或将其插入(如果不存在)?


83

我有下表的计数器:

CREATE TABLE cache (
    key text PRIMARY KEY,
    generation int
);

我想增加一个计数器,如果相应的行还不存在,则将其设置为零。有没有办法在标准SQL中没有并发问题的情况下做到这一点?该操作有时是事务的一部分,有时是独立的。

如果可能,SQL必须在SQLite,PostgreSQL和MySQL上未经修改地运行。

搜索产生了一些想法,这些想法可能遇到并发问题,或者特定于数据库:

  • 尝试到INSERT新行,UPDATE如果有错误。不幸的是,该错误会INSERT中止当前事务。

  • UPDATE该行,如果未修改任何行,则为INSERT新行。

  • MySQL有一个ON DUPLICATE KEY UPDATE子句。

编辑:感谢所有的伟大答复。看起来Paul是对的,而且没有一种可移植的方式来做到这一点。这对我来说是非常令人惊讶的,因为这听起来像是一个非常基本的操作。


6
您将找不到适用于所有这些RDBMS的单个解决方案。抱歉。
Paul Tomblin,2009年


Answers:


137

MySQL(以及随后的SQLite)也支持REPLACE INTO语法:

REPLACE INTO my_table (pk_id, col1) VALUES (5, '123');

这会自动识别主键并找到要更新的匹配行,如果找不到则插入新的行。


13
实际上,确切地说,MySQL的REPLACE总是执行插入操作,但是如果该行已经存在,它将首先删除该行。dev.mysql.com/doc/refman/4.1/en/replace.html
Evan

90
重要的是要了解它是一个插入+删除操作,并且永不更新。这样的结果是,您将始终要确保在进行替换时,应始终包括所有字段的数据。
Zoredache

2
@Zoredache从技术上讲,这是一个“删除,然后插入”,因为a(n)“插入+删除”实际上与删除相同,但是会分开头发。
Agi Hammerthief

2
@Agihammerthief有一个非常实际的区别,即新插入的行与已删除的行不会具有相同的主键。使用ON DUPLICATE,它将是相同的主键(除非您专门对其进行更改)。
Tim Strijdhorst,2013年


24

我将执行以下操作:

INSERT INTO cache VALUES (key, generation)
ON DUPLICATE KEY UPDATE (key = key, generation = generation + 1);

在代码或sql中将生成值设置为0,但使用ON DUP ...递增该值。我仍然认为这是语法。


1
老实说,这个答案应该是选定的答案。如果您没有将所有字段都提交给条目,则这是一种非破坏性的编辑。
约旦

9

ON DUPLICATE KEY UPDATE子句是最佳解决方案,因为:REPLACE先执行DELETE,然后执行INSERT,所以在非常短的时间内删除了记录,从而产生了非常小的可能性,使得查询可能会返回,而如果跳过了在REPLACE查询期间查看。

由于这个原因,我更喜欢INSERT ... ON DUPLICATE UPDATE...。

jmoz的解决方案是最好的:尽管我更喜欢SET语法而不是括号

INSERT INTO cache 
SET key = 'key', generation = 'generation'
ON DUPLICATE KEY 
UPDATE key = 'key', generation = (generation + 1)
;

4
REPLACE是原子的,因此没有任何时间不存在该行。
Brilliand


5

在PostgreSQL中,没有merge命令,实际上编写它并不容易-实际上存在使任务“有趣”的奇怪边缘情况。

最好的方法(例如:在最可能的条件下工作)是使用函数-如手册(merge_db)中所示。

如果您不想使用功能,通常可以摆脱:

updated = db.execute(UPDATE ... RETURNING 1)
if (!updated)
  db.execute(INSERT...)

只要记住它不是故障证明,它最终失败。



0

如果您没有通用的方式原子地更新或插入(例如,通过事务),则可以使用其他锁定方案。0字节的文件,系统互斥,命名管道等...


0

您可以使用插入触发器吗?如果失败,请进行更新。


命令起作用时,触发器(至少在PostgreSQL中)正在运行。也就是说,当基本命令失败时,您将无法运行触发器。

0

如果可以使用为您编写SQL的库,则可以使用Upsert(目前仅Ruby和Python):

Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
Pet.upsert({:name => 'Jerry'}, :color => 'brown')

这适用于MySQL,Postgres和SQLite3。

它在MySQL和Postgres中编写存储过程或用户定义函数(UDF)。它INSERT OR REPLACE在SQLite3中使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.