如何在 MySQL 中执行“如果不存在则插入”?

Posted

技术标签:

【中文标题】如何在 MySQL 中执行“如果不存在则插入”?【英文标题】:How can I do 'insert if not exists' in MySQL? 【发布时间】:2010-11-24 14:00:42 【问题描述】:

我从谷歌搜索开始,发现了一篇关于互斥表的文章 How to write INSERT if NOT EXISTS queries in standard SQL

我有一张包含约 1400 万条记录的表。如果我想以相同的格式添加更多数据,有没有办法确保我要插入的记录在不使用一对查询的情况下不存在(即,一个查询要检查,一个要插入是结果集是空)?

字段上的unique 约束是否保证insert 已经存在时会失败?

似乎只有 是一个约束,当我通过 php 发出插入时,脚本会发出嘶哑的声音。

【问题讨论】:

dev.mysql.com/doc/refman/5.0/en/if.html 请参阅***.com/questions/44550788/…,了解有关不燃烧 auto_inc 值的讨论。 @RickJames - 这是一个有趣的问题 .. 但不确定它是否与这个问题直接相关 :) 评论中提到过,另一个问题声称这个问题是“完全重复的”。因此,我认为将这些问题联系在一起以造福他人是个好主意。 哦,我从来没想过要看看侧边栏。 【参考方案1】:

如果您有一个可以使用ON DUPLICATE KEYINSERT IGNORE 检查的UNIQUE 索引,有几个答案涵盖了如何解决此问题。情况并非总是如此,并且由于UNIQUE 具有长度限制(1000 字节),您可能无法更改它。例如,我必须使用 WordPress (wp_postmeta) 中的元数据。

我终于用两个查询解决了:

UPDATE wp_postmeta SET meta_value = ? WHERE meta_key = ? AND post_id = ?;
INSERT INTO wp_postmeta (post_id, meta_key, meta_value) SELECT DISTINCT ?, ?, ? FROM wp_postmeta WHERE NOT EXISTS(SELECT * FROM wp_postmeta WHERE meta_key = ? AND post_id = ?);

查询 1 是常规的 UPDATE 查询,当相关数据集不存在时没有任何影响。查询 2 是一个 INSERT,它依赖于 NOT EXISTS,即 INSERT 仅在数据集不存在时执行。

【讨论】:

【参考方案2】:

这是一个 PHP 函数,仅当所有指定的列值都不存在于表中时才会插入一行。

如果其中一列不同,则将添加该行。

如果表为空,则添加行。

如果存在所有指定列都具有指定值的行,则不会添加该行。

 function insert_unique($table, $vars)
 
   if (count($vars)) 
     $table = mysql_real_escape_string($table);
     $vars = array_map('mysql_real_escape_string', $vars);

     $req = "INSERT INTO `$table` (`". join('`, `', array_keys($vars)) ."`) ";
     $req .= "SELECT '". join("', '", $vars) ."' FROM DUAL ";
     $req .= "WHERE NOT EXISTS (SELECT 1 FROM `$table` WHERE ";

     foreach ($vars AS $col => $val)
       $req .= "`$col`='$val' AND ";

     $req = substr($req, 0, -5) . ") LIMIT 1";

     $res = mysql_query($req) OR die();
     return mysql_insert_id();
   
   return False;
 

示例用法:

<?php
  insert_unique('mytable', array(
    'mycolumn1' => 'myvalue1',
    'mycolumn2' => 'myvalue2',
    'mycolumn3' => 'myvalue3'
    )
  );
?>

【讨论】:

如果你有大量的插入,会相当昂贵。 正确,但如果您需要添加特定检查,则效率很高 警告: mysql_* 扩展自 PHP 5.5.0 起已弃用,自 PHP 7.0.0 起已被删除。相反,应该使用mysqli 或PDO_MySQL 扩展名。在选择 MySQL API 时,另请参阅 MySQL API Overview 以获得更多帮助。【参考方案3】:

在 MySQL 中,ON DUPLICATE KEY UPDATE 或 INSERT IGNORE 可能是可行的解决方案。


基于mysql.com的ON DUPLICATE KEY UPDATE更新示例

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

UPDATE table SET c=c+1 WHERE a=1;

基于mysql.com的INSERT IGNORE示例

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    VALUES | VALUE (expr | DEFAULT,...),(...),...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

或者:

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    SET col_name=expr | DEFAULT, ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

或者:

INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    SELECT ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

【讨论】:

【参考方案4】:

如果可以接受异常,则任何简单的约束都可以完成工作。例子:

如果不是代理则主键 对列的唯一约束 多列唯一约束

对不起,如果这看起来很简单。我知道您与我们分享的链接看起来很糟糕。 ;-(

但我还是给出了这个答案,因为它似乎满足了你的需要。 (如果没有,它可能会触发您更新您的要求,这也是“一件好事”(TM))。

如果插入会破坏数据库唯一约束,则会在数据库级别引发异常,并由驱动程序中继。它肯定会停止你的脚本,但失败了。在 PHP 中必须可以解决这种情况......

【讨论】:

我对问题进行了澄清 - 您的回答是否仍然适用? 我相信确实如此。唯一约束将导致错误插入失败。注意:您必须在代码中处理此故障,但这是非常标准的。 现在我将坚持我接受的解决方案 - 但随着应用程序的增长,我会进一步研究处理 INSERT 失败等 INSERT IGNORE 基本上将所有错误更改为警告,以便您的脚本不会中断。然后,您可以使用命令SHOW WARNINGS 查看任何警告。另一个重要提示:UNIQUE 约束不适用于 NULL 值,即。 row1 (1, NULL) 和 row2 (1, NULL) 都将被插入(除非另一个约束,例如主键被破坏)。不幸。【参考方案5】:

使用INSERT IGNORE INTO table

还有INSERT … ON DUPLICATE KEY UPDATE语法,你可以在13.2.6.2 INSERT ... ON DUPLICATE KEY UPDATE Statement找到解释。


bogdan.org.ua发帖,根据Google's webcache发帖:

2007 年 10 月 18 日

开始:从最新的 MySQL 开始,标题中出现的语法不是 可能的。但是有几种非常简单的方法可以完成 预期使用现有功能。

有 3 种可能的解决方案:使用 INSERT IGNORE、REPLACE 或 插入……在重复的密钥更新上。

假设我们有一张桌子:

CREATE TABLE `transcripts` (
`ensembl_transcript_id` varchar(20) NOT NULL,
`transcript_chrom_start` int(10) unsigned NOT NULL,
`transcript_chrom_end` int(10) unsigned NOT NULL,
PRIMARY KEY (`ensembl_transcript_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

现在假设我们有一个自动管道导入成绩单 来自 Ensembl 的元数据,以及由于各种原因管道 可能在执行的任何步骤被破坏。因此,我们需要确保两个 事情:

    管道的重复执行不会破坏我们的 > 数据库
    重复执行不会因为“重复”而死 > 主键错误。

方法一:使用替换

很简单:

REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

如果记录存在,将被覆盖;如果还没有 存在,就会被创造。但是,使用这种方法效率不高 对于我们的案例:我们不需要覆盖现有记录,这很好 只是跳过它们。

方法2:使用INSERT IGNORE 也很简单:

INSERT IGNORE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

这里,如果“ensembl_transcript_id”已经存在于 数据库,它将被默默地跳过(忽略)。 (更准确地说, 这是 MySQL 参考手册中的一句话:“如果你使用 IGNORE 关键字,执行 INSERT 语句时发生的错误是 而是视为警告。例如,如果没有 IGNORE,那么一行 复制表中现有的 UNIQUE 索引或 PRIMARY KEY 值 导致重复键错误并且语句被中止。”)如果 记录尚不存在,它将被创建。

第二种方法有几个潜在的弱点,包括 如果出现任何其他问题,则不会中止查询(请参阅 手动的)。因此,如果之前没有经过测试,则应该使用它 忽略关键字。

方法 3:使用 INSERT … ON DUPLICATE KEY UPDATE:

第三个选项是使用INSERT … ON DUPLICATE KEY UPDATE 语法,而在 UPDATE 部分只是什么都不做,做一些无意义的事 (空)操作,例如计算 0+0(Geoffray 建议执行 id=id 分配给 MySQL 优化引擎忽略这个 手术)。这种方法的优点是它只忽略重复 关键事件,但仍会因其他错误而中止。

最后通知:这篇文章的灵感来自 Xaprb。我也建议 请参阅他关于编写灵活 SQL 查询的另一篇文章。

【讨论】:

我可以将它与“延迟”结合起来以加快脚本速度吗? 是的,延迟插入可能会加快您的速度。试试看 是的,请记住REPLACE INTO does DELETE then INSERT, not UPDATE INSERT … ON DUPLICATE KEY UPDATE 更好,因为它不会删除行,保留任何 auto_increment 列和其他数据。 只是为了通知大家。使用 INSERT … ON DUPLICATE KEY UPDATE 方法确实会增加任何插入失败的 AUTO_INCREMENT 列。可能是因为它并没有真正失败,而是更新了。【参考方案6】:

在没有已知主键的情况下更新或插入

如果您已经有一个唯一键或主键,INSERT INTO ... ON DUPLICATE KEY UPDATE ...REPLACE INTO ... 的其他答案应该可以正常工作(请注意,如果存在则替换为删除然后插入 - 因此不会部分更新现有值)。

但是,如果您有 some_column_idsome_type 的值,则已知它们的组合是唯一的。如果存在,您想更新some_value,如果不存在,则插入。并且您只想在一个查询中执行此操作(以避免使用事务)。这可能是一个解决方案:

INSERT INTO my_table (id, some_column_id, some_type, some_value)
SELECT t.id, t.some_column_id, t.some_type, t.some_value
FROM (
    SELECT id, some_column_id, some_type, some_value
    FROM my_table
    WHERE some_column_id = ? AND some_type = ?
    UNION ALL
    SELECT s.id, s.some_column_id, s.some_type, s.some_value
    FROM (SELECT NULL AS id, ? AS some_column_id, ? AS some_type, ? AS some_value) AS s
) AS t
LIMIT 1
ON DUPLICATE KEY UPDATE
some_value = ?

基本上,查询以这种方式执行(没有看起来那么复杂):

通过WHERE 子句匹配选择现有行。 将结果与潜在的新行(表 s)联合,其中列值是明确给出的(s.id 为 NULL,因此它将生成一个新的自动增量标识符)。 如果找到现有行,则表 s 中的潜在新行将被丢弃(由于表 t 上的 LIMIT 1),并且它将始终触发 ON DUPLICATE KEY,这将 UPDATE @ 987654333@专栏。 如果未找到现有行,则插入潜在的新行(如表 s 所示)。

注意:关系数据库中的每个表都应该至少有一个主自动增量id 列。如果你没有这个,添加它,即使你一开始并不需要它。这个“把戏”肯定是需要的。

【讨论】:

其他几个回答者提供了INSERT INTO ... SELECT FROM 格式。为什么你也这样做了? @warren 要么您没有阅读我的答案,要么您不理解它,要么我没有正确解释它。无论如何,让我强调以下几点:这不仅仅是一个常规的INSERT INTO... SELECT FROM... 解决方案。请给我一个相同答案的链接,如果你能找到它,我会删除这个答案,否则你会支持我的答案(交易?)。请务必验证您要链接的答案仅使用 1 个查询(用于更新 + 插入),没有事务,并且能够定位已知唯一的列的任何组合(因此单独的列不必须是唯一的)。【参考方案7】:

值得注意的是,无论语句成功与否,INSERT IGNORE 都会增加主键,就像普通的 INSERT 一样。

这将导致您的主键出现间隙,这可能会使程序员精神不稳定。或者,如果您的应用程序设计不佳并且依赖于完美的增量主键,则可能会令人头疼。

查看innodb_autoinc_lock_mode = 0(服务器设置,并带来轻微的性能影响),或首先使用 SELECT 以确保您的查询不会失败(这也带来了性能损失和额外代码)。

【讨论】:

为什么“主键中的空白”——甚至可能——“让程序员精神不稳定”?主键中总是会出现间隙 - 例如,每次删除记录时。【参考方案8】:

解决方案:

INSERT INTO `table` (`value1`, `value2`) 
SELECT 'stuff for value1', 'stuff for value2' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1) 

说明:

最里面的查询

SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1

用作WHERE NOT EXISTS-条件检测是否已经存在包含要插入数据的行。找到这种类型的一行后,查询可能会停止,因此LIMIT 1(微优化,可以省略)。

中间查询

SELECT 'stuff for value1', 'stuff for value2' FROM DUAL

表示要插入的值。 DUAL 指的是所有 Oracle 数据库中默认存在的特殊的一行一列表(请参阅https://en.wikipedia.org/wiki/DUAL_table)。在 MySQL-Server 版本 5.7.26 上,我在省略 FROM DUAL 时得到了有效查询,但旧版本(如 5.5.60)似乎需要 FROM 信息。通过使用WHERE NOT EXISTS,如果最里面的查询找到匹配数据,则中间查询返回一个空结果集。

外部查询

INSERT INTO `table` (`value1`, `value2`) 

插入数据,如果中间查询返回的话。

【讨论】:

你能提供更多关于如何使用它的信息吗? 此变体适用于表上不存在唯一键的情况(INSERT IGNOREINSERT ON DUPLICATE KEY 需要唯一键约束) 如果您在第 2 行使用“from dual”而不是“from table”,则不需要“limit 1”子句。 如果stuff for value1stuff for value2 相同怎么办?这会抛出一个Duplicate column name 我也更喜欢子查询中的SELECT 1 而不是SELECT *。更有可能通过索引来满足这一点。【参考方案9】:

尝试以下方法:

IF (SELECT COUNT(*) FROM beta WHERE name = 'John' > 0)
  UPDATE alfa SET c1=(SELECT id FROM beta WHERE name = 'John')
ELSE
BEGIN
  INSERT INTO beta (name) VALUES ('John')
  INSERT INTO alfa (c1) VALUES (LAST_INSERT_ID())
END

【讨论】:

Try This 答案在 *** 上的价值很低,因为它们对 OP 和成千上万的未来研究人员的教育作用很小。请编辑此答案以包括解决方案的工作原理以及为什么它是一个好主意。 如果要匹配的字段不是键的完美解决方案..!【参考方案10】:
REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

如果记录存在,将被覆盖;如果它还不存在,它将被创建。

【讨论】:

REPLACE 可能会删除行然后插入而不是更新。副作用是约束可能会删除其他对象并触发删除触发器。 来自 MySQL 手册:“REPLACE 仅在表具有 PRIMARY KEY 或 UNIQUE 索引时才有意义。否则,它等同于 INSERT,因为没有索引可用于确定是否有新的行重复另一个。”

以上是关于如何在 MySQL 中执行“如果不存在则插入”?的主要内容,如果未能解决你的问题,请参考以下文章

mysql:如果不存在则插入记录,否则返回记录的ID

执行 Mysql 多选的最有效方法(如果不存在则插入)

SQL 查询 - 如果存在则更新,否则插入

Mysql插入,如果不存在则插入另一个[重复]

MySQL insert

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