如何更新表中的一行或插入它,如果它不存在?
我有下面的表格:
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 INTO
expression式兼容。
我会做如下的事情:
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
。