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

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

【讨论】:

以上是关于如果表中的行不存在,我如何更新或插入它?的主要内容,如果未能解决你的问题,请参考以下文章

HSQLDB 如果不存在则插入,如果存在则更新

如果 mysql 数据库中的表中不存在,则更新或插入多条记录

MYSQL - 仅当 LEFT JOIN 中的行不存在时才选择

插入 MySQL 表或更新(如果存在)

插入 MySQL 表或更新(如果存在)

使行不可变并允许插入