【问题标题】:conditional on duplicate key update以重复密钥更新为条件
【发布时间】:2009-06-25 16:27:03
【问题描述】:

我正在尝试插入一个新行,但如果键已经存在,我只想在表中的某个其他值不同时更新该行。这可能在 mysql 查询/语句中吗?

我的表格由以下列组成:hat、mittens、name、last_update

帽子+手套构成唯一索引(比如“帽子”和“手套”的值是颜色)

假设这已经在表格中:

1. 帽子=蓝色手套=绿色名称=乔治 last_update=星期二 2. hat=red mittens=green name=bill last_update=monday

在一个新键上,我想像往常一样插入。在重复键上,我只想在名称更改时进行更新,否则忽略。这样做的原因是我想保留 last_update 值(时间戳)。

hat=yellow mittens=purple name=jimmy -- 插入新行 帽子=蓝色手套=绿色名称=乔治-忽略 hat=blue mittens=green name=betty -- 更新行

如果不使用单独的语句首先查找现有行、比较值然后在必要时发出更新,这是否可行?如果是这样,语法是什么?


感谢您的回复。我都试过了。事实上,只使用一个简单的 UPDATE 语句,如

update tbl set name='george' where hat='blue' and mittens='green'

导致没有行被更新。但是,使用任一

 INSERT INTO tbl (hat,mittens,name) VALUES ('blue','green','george') ON DUPLICATE KEY UPDATE name='george';

INSERT INTO tbl (hat, mittens, name) VALUES ('blue','green','george') ON DUPLICATE KEY UPDATE name=CASE WHEN name <> VALUES(name) THEN VALUES(name) ELSE name END;

不知何故导致行被更新(并且时间戳改变)。

FWIW,这是我正在使用的表:

CREATE TABLE `tbl` (
`hat` varchar(11) default NULL,
`mittens` varchar(11) default NULL,
`name` varchar(11) default NULL,
`stamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
UNIQUE KEY `clothes` (`hat`,`mittens`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

MySQL 是 4.1.22 版(也许这很重要?) 再次感谢所有回复。

【问题讨论】:

  • 我猜是你的 MySQL 版本是这里的问题。在对 4.1 分支的积极支持结束(2006 年 12 月 31 日)之后,看起来他们使用 INSERT ... ON DUPLICATE KEY UPDATE 在 5.0 分支中修复了一个错误(bugs.mysql.com/bug.php?id=28904)。我在我的机器上重新创建了你的表和测试用例,它按预期工作,我正在运行 MySQL Server 5.0.67。如果您无法更新 MySQL 安装,那么您可能会因为没有“单一查询”解决方案而陷入困境。
  • 确认。两种解决方案都适用于 5.0.45(不幸的是,这只是一个开发服务器)。再次感谢!
  • 您尝试了我建议的两个查询中的第二个吗?手动控制标记(last_update)列发生了什么的那个?我相信即使存在该错误也应该可以工作,因为它明确决定是否使用另一个案例语句更新列。
  • 我有时间安装 4.1.22 版,以下单个语句按预期工作,除非名称更改,否则不会更新戳列:mysql> INSERT INTO tbl (hat,mittens,name) VALUES ('blue','green','george') ON DUPLICATE KEY UPDATE name = CASE WHEN name VALUES(name) THEN VALUES(name) ELSE name END, stamp = CASE WHEN name VALUES(name) THEN now() ELSE 戳 END;请参阅我的答案以获取验证行为的测试用例日志。

标签: sql mysql


【解决方案1】:

您可以在ON DUPLICATE KEY 语法中使用普通的sql 结构。因此,为了在插入期间进行条件更新,您可以执行以下操作:

INSERT INTO tbl (hat, mittens, name) 
VALUES ('yellow','purple','jimmy')
ON DUPLICATE KEY UPDATE name = CASE WHEN name <> VALUES(name) 
                                    THEN VALUES(name) ELSE name END;

这会将值更改为您提供给插入语句的值,当它与行中的值不同时,如果它没有更改,则会将值设置为已经存在的值,并且将导致 MySQL 不执行任何操作正如 Quassnoi 指出的那样,保留 last_update 时间戳的行。

如果您想 100% 确保您不依赖 MySQL 的行为,如果您将值设置为自身,它不会更新行,您可以执行以下操作来强制时间戳:

INSERT INTO tbl (hat, mittens, name) 
VALUES ('yellow','purple','jimmy')
ON DUPLICATE KEY UPDATE name = CASE WHEN name <> VALUES(name) 
                                    THEN VALUES(name) ELSE name END
                      , last_update = CASE WHEN name <> VALUES(name) 
                                      THEN now() ELSE last_update END;

这只会在名称更改时将 last_update 更新为 now(),否则它将告诉 MySQL 保留 last_update 的值。

此外,在语句的 ON DUPLICATE KEY 部分中,您可以通过名称引用表中的列,并且您可以使用 VALUES(column_name) 函数获取您提供给插入语句值部分的值。


以下是一个日志,显示提供的最后一条语句即使在 4.1 上也可以工作,而其他语句由于 5.0 版中修复的错误而无法工作。

C:\mysql\bin>mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1 to server version: 4.1.22-community

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> show databases;
+----------+
| Database |
+----------+
| mysql    |
| test     |
+----------+
2 rows in set (0.00 sec)

mysql> use test;
Database changed
mysql> show tables;
Empty set (0.00 sec)

mysql> CREATE TABLE `tbl` (
    -> `hat` varchar(11) default NULL,
    -> `mittens` varchar(11) default NULL,
    -> `name` varchar(11) default NULL,
    -> `stamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
    -> UNIQUE KEY `clothes` (`hat`,`mittens`)
    -> ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO tbl (hat,mittens,name) VALUES ('blue','green','george');
Query OK, 1 row affected (0.00 sec)

mysql> select * from tbl;
+------+---------+--------+---------------------+
| hat  | mittens | name   | stamp               |
+------+---------+--------+---------------------+
| blue | green   | george | 2009-06-27 12:15:16 |
+------+---------+--------+---------------------+
1 row in set (0.00 sec)

mysql> INSERT INTO tbl (hat,mittens,name) VALUES ('blue','green','george') ON DUPLICATE KEY UPDATE name='george';
Query OK, 2 rows affected (0.00 sec)

mysql> select * from tbl;
+------+---------+--------+---------------------+
| hat  | mittens | name   | stamp               |
+------+---------+--------+---------------------+
| blue | green   | george | 2009-06-27 12:15:30 |
+------+---------+--------+---------------------+
1 row in set (0.00 sec)

mysql> INSERT INTO tbl (hat, mittens, name) VALUES ('blue','green','george') ON DUPLICATE KEY UPDATE name=CASE WHEN name <> VALUES(name) THEN VALUES(name) ELSE name END;
Query OK, 2 rows affected (0.00 sec)

mysql> select * from tbl;
+------+---------+--------+---------------------+
| hat  | mittens | name   | stamp               |
+------+---------+--------+---------------------+
| blue | green   | george | 2009-06-27 12:15:42 |
+------+---------+--------+---------------------+
1 row in set (0.00 sec)

mysql> INSERT INTO tbl (hat,mittens,name) VALUES ('blue','green','george') ON DUPLICATE KEY UPDATE name = CASE WHEN name <> VALUES(name) THEN VALUES(name) ELSE name END, stamp = CASE WHEN name <> VALUES(name) THEN now() ELSE stamp END;
Query OK, 2 rows affected (0.00 sec)

mysql> select * from tbl;
+------+---------+--------+---------------------+
| hat  | mittens | name   | stamp               |
+------+---------+--------+---------------------+
| blue | green   | george | 2009-06-27 12:15:42 |
+------+---------+--------+---------------------+
1 row in set (0.00 sec)

mysql>

如果您有任何问题,请告诉我。

HTH,

-地平

【讨论】:

  • 对我来说,找出 代表什么(不等于!)有点困难,所以我只是想我会在评论中添加这个,以防将来有人想知道. :)
  • 您好,帮助我更新时有多列时如何查询
  • 这个旧答案仍然是最好的方法吗?如果不是 1 个非关键字段(名称),而是有 50 个字段来比较更改。你会在“last_update = CASE WHEN name VALUES(name) THEN now() ELSE last_update END 中列出 50 个 case 语句吗? ;"
【解决方案2】:

您需要INSERT ... ON DUPLICATE KEY UPDATE 语法。

您的查询将如下所示:

    INSERT INTO tbl (hat,mittens,name) VALUES ('blue','green','george')
    ON DUPLICATE KEY UPDATE name='george';

如果您已经有帽子/手套/名称的蓝色/绿色/乔治记录,则实际上不会执行任何更新,并且您的时间戳也不会更新。但是,如果您有一个带有 blue/green/betty 的记录,那么 'betty' 将被 'george' 覆盖,并且您的时间戳将被更新。

【讨论】:

    【解决方案3】:

    您无需执行任何操作,这是默认行为。

    如果您的查询为UPDATE 选择了一行,但更新后的值保持不变,如下所示:

    UPDATE  table
    SET     col = col
    

    ,时间戳也保持不变。

    这一行甚至不算受影响,查询将返回0 rows affected

    【讨论】:

      【解决方案4】:

      如果您正在执行多个INSERT(通过SELECT 或向VALUES 提供多行),您可以执行以下操作:

      INSERT INTO tbl (hat,mittens,name) VALUES
          ('yellow','purple','jimmy'),
          ('blue','green','george'),
          ('blue','green','betty')
      ON DUPLICATE KEY UPDATE name = VALUES(name);
      

      这将:

      • 分别插入不重复的行。到 UNIQUE 索引
      • 为其他列更新name 列(当新值不同时)

      警告:documentation on the ON DUPLICATE syntax 中的 cmets 表示之后的 mysql_affected_rows() 结果不可靠。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-31
        • 2018-06-24
        • 2013-03-07
        • 2012-08-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多