如果表中的行不存在,我如何更新或插入它?
Posted
技术标签:
【中文标题】如果表中的行不存在,我如何更新或插入它?【英文标题】:How do I UPDATE a row in a table or INSERT it if it doesn't exist? 【发布时间】:2010-10-15 23:50:31 【问题描述】:我有下面的计数器表:
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 是对的,而且没有一种单一的、可移植的方式来做到这一点。这让我很惊讶,因为这听起来像是一个非常基本的操作。
【问题讨论】:
您不会找到适用于所有这些 RDBMS 的单一解决方案。对不起。 SQLite - UPSERT *not* INSERT or REPLACE的可能重复 Solutions for INSERT OR UPDATE on SQL Server的可能重复 【参考方案1】:MySQL(以及随后的 SQLite)也支持 REPLACE INTO 语法:
REPLACE INTO my_table (pk_id, col1) VALUES (5, '123');
这会自动识别主键并找到要更新的匹配行,如果没有找到则插入新行。
文档:https://dev.mysql.com/doc/refman/8.0/en/replace.html
【讨论】:
其实,具体来说,MySQL的REPLACE总是会进行插入操作,但是如果该行已经存在,它会先删除该行。 dev.mysql.com/doc/refman/4.1/en/replace.html 重要的是要理解它是一个插入+删除并且永远不会更新。这样做的结果是,您总是希望确保在进行替换时,您应该始终包含所有字段的数据。 @Zoredache 从技术上讲,它是一个“删除,然后插入”,因为(n)“插入 + 删除”实际上与删除相同,但这是分裂的头发。 @Agihammerthief 有一个非常真实的区别,即新插入的行不会与已删除的行具有相同的主键。使用 ON DUPLICATE 它将是相同的主键(除非您专门更改它)。【参考方案2】:如果replacing a row 已经存在,SQLite 支持它:
INSERT OR REPLACE INTO [...blah...]
您可以将其缩短为
REPLACE INTO [...blah...]
添加此快捷方式是为了与 MySQL REPLACE INTO
表达式兼容。
【讨论】:
它要求您在值中定义一个PRAMARY KEY
。【参考方案3】:
我会做如下的事情:
INSERT INTO cache VALUES (key, generation)
ON DUPLICATE KEY UPDATE (key = key, generation = generation + 1);
在代码或 sql 中将生成值设置为 0,但使用 ON DUP... 来增加值。我认为无论如何都是这样的语法。
【讨论】:
老实说,这个答案应该是选定的答案。如果您没有将所有字段都提交到条目,则这是一种非破坏性编辑。【参考方案4】: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)
;
【讨论】:
REPLACE 是原子的,因此没有不存在该行的时间段。【参考方案5】:我不知道你会找到一个平台中立的解决方案。
这通常称为“UPSERT”。
查看一些相关讨论:
Solutions for INSERT OR UPDATE on SQL Server SQL Server 2005 implementation of MySQL REPLACE INTO?【讨论】:
该解决方案真的适用吗?和便携?【参考方案6】:在 PostgreSQL 中没有合并命令,实际上编写它并非易事 - 实际上有一些奇怪的边缘情况使任务“有趣”。
最好的(如:在最可能的条件下工作)方法是使用函数 - 例如manual (merge_db) 中所示的函数。
如果你不想使用函数,你可以通常侥幸:
updated = db.execute(UPDATE ... RETURNING 1)
if (!updated)
db.execute(INSERT...)
请记住,它不是防错的,它会最终失败。
【讨论】:
【参考方案7】:标准 SQL 为此任务提供了 MERGE 语句。并非所有 DBMS 都支持 MERGE 语句。
【讨论】:
【参考方案8】:如果您没有通用的方式来自动更新或插入(例如,通过事务),那么您可以回退到另一个锁定方案。一个 0 字节的文件、系统互斥体、命名管道等......
【讨论】:
【参考方案9】:您可以使用插入触发器吗?如果失败,请进行更新。
【讨论】:
触发器(至少在 PostgreSQL 中)在命令工作时正在运行。即当基本命令失败时,您不能运行触发器。【参考方案10】:如果您可以使用为您编写 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
。
【讨论】:
以上是关于如果表中的行不存在,我如何更新或插入它?的主要内容,如果未能解决你的问题,请参考以下文章
如果 mysql 数据库中的表中不存在,则更新或插入多条记录