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

发布于 2024-07-16 08:42:59 字数 671 浏览 8 评论 0原文

我有以下计数器表:

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

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

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

搜索产生了几个想法,这些想法要么存在并发问题,要么特定于数据库:

  • 尝试INSERT新行,如果出现错误则UPDATE 。 不幸的是,INSERT 上的错误会中止当前事务。

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

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

编辑:感谢所有精彩的回复。 看来保罗是对的,而且没有一种单一的、可移植的方法可以做到这一点。 这让我感到非常惊讶,因为这听起来像是一个非常基本的操作。

I have the following table of counters:

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

I would like to increment one of the counters, or set it to zero if the corresponding row doesn't exist yet. Is there a way to do this without concurrency issues in standard SQL? The operation is sometimes part of a transaction, sometimes separate.

The SQL must run unmodified on SQLite, PostgreSQL and MySQL, if possible.

A search yielded several ideas which either suffer from concurrency issues, or are specific to a database:

  • Try to INSERT a new row, and UPDATE if there was an error. Unfortunately, the error on INSERT aborts the current transaction.

  • UPDATE the row, and if no rows were modified, INSERT a new row.

  • MySQL has an ON DUPLICATE KEY UPDATE clause.

EDIT: Thanks for all the great replies. It looks like Paul is right, and there's not a single, portable way of doing this. That's quite surprising to me, as it sounds like a very basic operation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

迷离° 2024-07-23 08:42:59

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 (and subsequently SQLite) also support the REPLACE INTO syntax:

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

This automatically identifies the primary key and finds a matching row to update, inserting a new one if none is found.

Documentation: https://dev.mysql.com/doc/refman/8.0/en/replace.html

神仙妹妹 2024-07-23 08:42:59

SQLite 支持替换行(如果已存在):

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

您可以将其缩短

REPLACE INTO [...blah...]

为此快捷方式已添加为与 MySQL REPLACE INTO 表达式兼容。

SQLite supports replacing a row if it already exists:

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

You can shorten this to

REPLACE INTO [...blah...]

This shortcut was added to be compatible with the MySQL REPLACE INTO expression.

天赋异禀 2024-07-23 08:42:59

我会执行如下操作:

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

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

I would do something like the following:

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

Setting the generation value to 0 in code or in the sql but the using the ON DUP... to increment the value. I think that's the syntax anyway.

神爱温柔 2024-07-23 08:42:59

ON DUPLICATE KEY UPDATE 子句是最佳解决方案,因为:
REPLACE 会先执行 DELETE,然后再执行 INSERT,因此在很短的一段时间内,记录会被删除,从而导致如果在 REPLACE 查询期间查看页面,则查询可能会跳过该记录而返回。

出于这个原因,我更喜欢插入...重复更新...。

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

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

the ON DUPLICATE KEY UPDATE clause is the best solution because:
REPLACE does a DELETE followed by an INSERT so for an ever so slight period the record is removed creating the ever so slight possibility that a query could come back having skipped that if the page was viewed during the REPLACE query.

I prefer INSERT ... ON DUPLICATE UPDATE ... for that reason.

jmoz's solution is the best:
though I prefer the SET syntax to the parentheses

INSERT INTO cache 
SET key = 'key', generation = 'generation'
ON DUPLICATE KEY 
UPDATE key = 'key', generation = (generation + 1)
;
呢古 2024-07-23 08:42:59

我不知道您是否会找到平台中立的解决方案。

这通常称为“UPSERT”。

请参阅一些相关讨论:

I don't know that you are going to find a platform-neutral solution.

This is commonly called an "UPSERT".

See some related discussions:

南风几经秋 2024-07-23 08:42:59

在 PostgreSQL 中没有合并命令,实际上编写它并不简单 - 实际上有一些奇怪的边缘情况使任务“有趣”。

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

如果您不想使用函数,您通常可以这样做:

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

只要记住它不是防错的,它最终将会失败。

In PostgreSQL there is no merge command, and actually writing it is not trivial - there are actually strange edge cases that make the task "interesting".

The best (as in: working in the most possible conditions) approach, is to use function - such as one shown in manual (merge_db).

If you don't want to use function, you can usually get away with:

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

Just remember that it is not fault proof and it will fail eventually.

岁月苍老的讽刺 2024-07-23 08:42:59

标准 SQL 提供了用于此任务的 MERGE 语句。 并非所有 DBMS 都支持 MERGE 语句。

Standard SQL provides the MERGE statement for this task. Not all DBMS support the MERGE statement.

怼怹恏 2024-07-23 08:42:59

如果您没有原子更新或插入的通用方法(例如,通过事务),那么您可以回退到另一个锁定方案。 0 字节文件、系统互斥体、命名管道等...

If you don't have a common way to atomically update or insert (e.g., via a transaction) then you can fallback to another locking scheme. A 0-byte file, system mutex, named pipe, etc...

书信已泛黄 2024-07-23 08:42:59

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

Could you use an insert trigger? If it fails, do an update.

毁梦 2024-07-23 08:42:59

如果您同意使用为您编写 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

If you're OK with using a library that writes the SQL for you, then you can use Upsert (currently Ruby and Python only):

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

That works across MySQL, Postgres, and SQLite3.

It writes a stored procedure or user-defined function (UDF) in MySQL and Postgres. It uses INSERT OR REPLACE in SQLite3.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文