以重复密钥更新为条件

时间:2009-06-25 16:27:04

标签: sql mysql

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

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

帽子+连指手套构成独特的指数(比如“帽子”和“连指手套”的颜色都是颜色)

我们假设这已经在表格中了:

1. hat=blue mittens=green name=george last_update=tuesday
2. hat=red mittens=green name=bill last_update=monday

在新密钥上,我想像往常一样插入。在重复键上,我想仅在名称更改时进行更新,否则忽略。原因是我想保留last_update值(timestamp)。

hat=yellow mittens=purple name=jimmy -- insert new row
hat=blue mittens=green name=george -- ignore 
hat=blue mittens=green name=betty -- update row

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


感谢您的回复。我尝试了所有这些。实际上,只使用像

这样的简单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(也许这很重要?) 再次,我对所有答复表示赞赏。

4 个答案:

答案 0 :(得分:17)

您可以在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;

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

如果您想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)函数获取您提供给insert语句值部分的值。


以下是一个日志,显示提供的最后一个语句甚至可以在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,

-Dipin

答案 1 :(得分:3)

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

您的查询将如下所示:

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

如果你的hat / mittens / name已经有蓝/绿/乔治的记录,那么实际上不会执行UPDATE,并且你的时间戳也不会更新。但是如果你有蓝/绿/贝蒂的记录,那么'betty'会被'george'覆盖,你的时间戳会被更新。

答案 2 :(得分:0)

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

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

UPDATE  table
SET     col = col

,时间戳也保持不变。

此行甚至不计入受影响,查询将返回0 rows affected

答案 3 :(得分:0)

如果您要执行多个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);

这将:

  • 插入不重复的行w / resp。到UNIQUE索引
  • 更新其他人的name列(当新值不同时)

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