如何更新表中的一行或插入它,如果它不存在?

我有下面的表格:

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

我想递增其中一个计数器,或者如果相应的行不存在,则将其设置为零。 有没有办法做到这一点没有并发问题在标准的SQL? 操作有时是交易的一部分,有时是分开的。

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

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

  • 尝试INSERT一个新行,并UPDATE是否有错误。 不幸的是, INSERT上的错误会中止当前事务。

  • UPDATE该行,如果没有行被修改,则INSERT一个新行。

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

编辑:感谢所有伟大的答复。 看起来保罗是对的,而且没有一种便携式的方法可以做到这一点。 这对我来说是相当惊人的,因为这听起来像是一个非常基本的操作。

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

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

这会自动识别主键并find匹配的行进行更新,如果没有find,则插入新的主键。

如果SQLite已经存在,SQLite支持replace它:

 INSERT OR REPLACE INTO [...blah...] 

你可以缩短这个

 REPLACE INTO [...blah...] 

这个快捷方式被添加到与MySQL的REPLACE INTOexpression式兼容。

我会做如下的事情:

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

在代码或sql中将代数值设置为0,但是使用ON DUP …来增加值。 无论如何,我认为这是语法。

我不知道你会find一个平台中立的解决scheme。

这通常被称为“UPSERT”。

看到一些相关的讨论:

  • SQL Server上的INSERT OR UPDATE解决scheme
  • MySQL的SQL Server 2005实现REPLACE INTO?

ON DUPLICATE KEY UPDATE子句是最好的解决scheme,因为:REPLACE执行一个DELETE,然后是一个INSERT,因此在这么短的一段时间内,logging被删除,创build这样一个小的可能性,查询可能会跳过,如果页面是在REPLACE查询期间查看。

我更喜欢INSERT …在DUPLICATE UPDATE …出于这个原因。

jmoz的解决scheme是最好的:虽然我更喜欢SET语法括号

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

在PostgreSQL中没有合并命令,实际上编写它并不是微不足道的 – 实际上有奇怪的边缘情况使得这个任务变得“有趣”。

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

如果你不想使用function,你通常可以逃避:

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

请记住,这不是故障certificate,最终失败。

标准SQL为此任务提供了MERGE语句。 不是所有的DBMS都支持MERGE语句。

如果你没有一个通用的方式来自动更新或插入(例如,通过一个事务),那么你可以回退到另一个lockingscheme。 一个0字节的文件,系统互斥,命名pipe道等…

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

如果你可以使用为你写SQL的库,那么你可以使用Upsert (目前只有Ruby和Python):

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

这适用于MySQL,Postgres和SQLite3。

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