MySQL插入非PRIMARY键的重复更新

时间:2015-11-03 09:11:54

标签: mysql

我对重复更新查询中的插入感到困惑。 我有MySQL表,结构如下:

  • record_id(PRIMARY,UNIQUE)
  • person_id(UNIQUE)
  • SOME_TEXT
  • some_other_text

如果我的table.person中存在id,则我想为person更新some_text和some_other_text值,否则在此表中插入新记录。如果person_id不是PRIMARY,怎么办呢?

6 个答案:

答案 0 :(得分:9)

您需要一个查询,检查是否存在与record_id(或person_id)相关的任何行。如果存在更新它,否则插入新行

IF EXISTS (SELECT * FROM table.person WHERE record_id='SomeValue')
    UPDATE table.person 
    SET some_text='new_some_text', some_other_text='some_other_text' 
    WHERE record_id='old_record_id'
ELSE
    INSERT INTO table.person (record_id, person_id, some_text, some_other_text) 
    VALUES ('new_record_id', 'new_person_id', 'new_some_text', 'new_some_other_text')

另一种更好的方法是

UPDATE table.person SET (...) WHERE person_id='SomeValue'
IF ROW_COUNT()=0
    INSERT INTO table.person (...) VALUES (...)

答案 1 :(得分:7)

您的问题非常有效。这是一个非常常见的要求。由于MySQL提供的功能,大多数人都会弄错。

  • 要求:插入除非存在PRIMARY密钥,否则请更新
  • 常用方法:ON DUPLICATE KEY UPDATE
  • 该方法的结果令人不安:插入除非PRIMARY 或任何UNIQUE存在,否则更新

ON DUPLICATE KEY UPDATE可能出现什么问题?您插入了一个假定的新记录,其中包含一个新的PRIMARY键值(例如一个UUID),但您的UNIQUE键恰好有一个重复的值。

您想要的是一个正确的例外,表示您正在尝试将副本插入UNIQUE列。

但你得到的是不受欢迎的UPDATE! MySQL将采用冲突记录并开始覆盖其值。如果这种情况无意中发生,则您已经删除了旧记录,并且对旧记录的任何传入引用现在都引用了新记录。由于您可能不会告诉查询更新PRIMARY列,因此无法找到新的UUID。如果您遇到这些数据,它可能没有任何意义,您将不知道它来自何处。

除非存在PRIMARY密钥,否则我们需要实际 插入的解决方案,否则更新

我们将使用包含两个语句的查询:

  1. 更新PRIMARY键值匹配的位置(影响0或1行)。
  2. 如果PRIMARY键值不存在(插入1或0行),则插入。
  3. 这是查询:

    UPDATE my_table SET
    unique_name = 'one', update_datetime = NOW()
    WHERE id = 1;
    
    INSERT INTO my_table
    SELECT 1, 'one', NOW()
    FROM my_table
    WHERE id = 1
    HAVING COUNT(*) = 0;
    

    这些查询中只有一个会产生影响。 UPDATE很简单。至于INSERTWHERE id = 1如果id存在则会产生一行,如果不存在则不会产生行。 HAVING COUNT(*) = 0将其反转,如果id为new,则生成一行,如果已存在则不生成行。

    我已经探索了相同想法的其他变体,例如使用LEFT JOINWHERE,但它们看起来更复杂。欢迎改进。

答案 2 :(得分:5)

  

13.2.5.3 INSERT ... ON DUPLICATE KEY UPDATE Syntax

     

如果指定ON DUPLICATE KEY UPDATE,则插入一行   会在UNIQUE索引或PRIMARY KEY,MySQL中导致重复值   执行旧行的更新。

示例:

DELIMITER //

DROP PROCEDURE IF EXISTS `sp_upsert`//
DROP TABLE IF EXISTS `table_test`//

CREATE TABLE `table_test` (
  `record_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `person_id` INT UNSIGNED NOT NULL,
  `some_text` VARCHAR(50),
  `some_other_text` VARCHAR(50),
  UNIQUE KEY `record_id_index` (`record_id`),
  UNIQUE KEY `person_id_index` (`person_id`)
)//

INSERT INTO `table_test`
  (`person_id`, `some_text`, `some_other_text`)
VALUES
  (1, 'AAA', 'XXX'),
  (2, 'BBB', 'YYY'),
  (3, 'CCC', 'ZZZ')//

CREATE PROCEDURE `sp_upsert`(
  `p_person_id` INT UNSIGNED,
  `p_some_text` VARCHAR(50),
  `p_some_other_text` VARCHAR(50)
)
BEGIN
  INSERT INTO `table_test`
    (`person_id`, `some_text`, `some_other_text`)
  VALUES
    (`p_person_id`, `p_some_text`, `p_some_other_text`)
  ON DUPLICATE KEY UPDATE `some_text` = `p_some_text`,
                          `some_other_text` = `p_some_other_text`;
END//

DELIMITER ;

mysql> CALL `sp_upsert`(1, 'update_text_0', 'update_text_1');
Query OK, 2 rows affected (0.00 sec)

mysql> SELECT
    ->   `record_id`,
    ->   `person_id`,
    ->   `some_text`,
    ->   `some_other_text`
    -> FROM
    ->   `table_test`;
+-----------+-----------+---------------+-----------------+
| record_id | person_id | some_text     | some_other_text |
+-----------+-----------+---------------+-----------------+
|         1 |         1 | update_text_0 | update_text_1   |
|         2 |         2 | BBB           | YYY             |
|         3 |         3 | CCC           | ZZZ             |
+-----------+-----------+---------------+-----------------+
3 rows in set (0.00 sec)

mysql> CALL `sp_upsert`(4, 'new_text_0', 'new_text_1');
Query OK, 1 row affected (0.00 sec)

mysql> SELECT
    ->   `record_id`,
    ->   `person_id`,
    ->   `some_text`,
    ->   `some_other_text`
    -> FROM
    ->   `table_test`;
+-----------+-----------+---------------+-----------------+
| record_id | person_id | some_text     | some_other_text |
+-----------+-----------+---------------+-----------------+
|         1 |         1 | update_text_0 | update_text_1   |
|         2 |         2 | BBB           | YYY             |
|         3 |         3 | CCC           | ZZZ             |
|         5 |         4 | new_text_0    | new_text_1      |
+-----------+-----------+---------------+-----------------+
4 rows in set (0.00 sec)

SQL Fiddle demo

答案 3 :(得分:1)

我碰到这篇文章是因为我需要标题中写的内容,并且找到了一个非常方便的解决方案,但是这里没有人提到它,因此我想到将其粘贴到这里。注意,如果要启动数据库表,此解决方案非常方便。在这种情况下,当您创建对应的表时,照常定义主键等,对于要唯一的列组合,只需添加

UNIQUE(column_name1,column_name2,...)
CREATE TABLE语句的末尾

,对于要唯一的指定列的任何组合。像这样,根据this page here,“ MySQL会使用column_name1和column_name2列中的值组合来评估唯一性”,如果尝试插入已经具有column_name1值组合的插入,则会报告错误和column_name2您在插入中提供的。这种创建数据库表的方式与相应的INSERT ON DUPLICATE KEY语法相结合似乎是最适合我的解决方案。在实际开始使用表格之前,只需要仔细考虑一下即可;设置数据库表时。

答案 4 :(得分:0)

我的方法怎么样?

假设您有一个表格,其中包含自动增量ID和三个文本列。您希望插入/更新column3的值,其中column1和column2中的值是(非唯一)键。

我使用此查询(没有显式锁定表):

insert into myTable (id, col1, col2, col3)
select tmp.id, 'col1data', 'col2data', 'col3data' from
(select id from myTable where col1 = 'col1data' and col2 = 'col2data' union select null as id limit 1) tmp
on duplicate key update col3 = values(col3)

有什么问题吗?对我来说,它按我想要的方式工作。

答案 5 :(得分:0)

一种灵活的解决方案应该保留INSERT ... ON DUPLICATE KEY UPDATE提供的原子性,并且无论它是否为autocommit=true都可以工作,并且不依赖于隔离级别为REPEATABLE READ或更高的事务。

任何在多个语句之间执行“先检查后行动”的解决方案都不满足。

以下是选项:

如果插入次数往往多于更新次数:

INSERT INTO table (record_id, ..., some_text, some_other_text) VALUES (...);

IF <duplicate entry for primary key error>
  UPDATE table SET some_text = ..., some_other_text = ... WHERE record_id = ...;

  IF affected-rows = 0
    -- retry from INSERT OR ignore this conflict and defer to the other session

如果更新往往多于插入内容:

UPDATE table SET some_text = ..., some_other_text = ... WHERE record_id = ...;

IF affected-rows = 0
  INSERT INTO table (record_id, ..., some_text, some_other_text) VALUES (...);

  IF <duplicate entry for primary key error>
    -- retry from UPDATE OR ignore this conflict and defer to the other session

如果您不介意丑陋,则可以实际使用INSERT ... ON DUPLICATE KEY UPDATE并在一条语句中完成:

INSERT INTO table (record_id, ..., some_text, some_other_text) VALUES (...)
    ON DUPLICATE KEY UPDATE
      some_text = if(record_id = VALUES(record_id), VALUES(some_text), some_text),
      some_other_text = if(record_id = VALUES(record_id), VALUES(some_other_text), some_other_text)

IF affected-rows = 0
  -- handle this as a unique check constraint violation

注意:在这些示例中,affected-rows表示受影响的行,而不是 found 行。两者可能会混淆,因为a single parameter switches which of these values the client is returned

还请注意,如果在执行更新时未实际修改some_textsome_other_text(并且记录也未更改),则对affected-rows = 0的检查将失败。