989

我从谷歌搜索开始,发现文章如何在标准 SQL 中编写 INSERT if NOT EXISTS 查询,其中讨论了互斥表。

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

如果字段已经存在unique,对字段的约束是否保证会失败?insert

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

4

11 回答 11

926

使用INSERT IGNORE INTO table.

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


根据Google 的 webcachebogdan.org.ua发布:

2007 年 10 月 18 日

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

有 3 种可能的解决方案:使用 INSERT IGNORE、REPLACE 或 INSERT ... ON DUPLICATE KEY UPDATE。

假设我们有一张桌子:

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 导入转录元数据的自动管道,并且由于各种原因,管道可能在执行的任何步骤中被破坏。因此,我们需要确保两件事:

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

方法一:使用替换

这很简单:

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 值会导致重复键错误并且语句被中止。”。)如果该记录尚不存在,它将被创建。

第二种方法有几个潜在的弱点,包括在发生任何其他问题时不会中止查询(请参阅手册)。因此,如果之前没有使用 IGNORE 关键字进行测试,则应该使用它。

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

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

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

于 2009-09-01T09:02:01.727 回答
284

解决方案:

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-condition 检测是否已经存在包含要插入的数据的行。找到这种类型的一行后,查询可能会停止,因此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`) 

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

于 2010-06-11T18:38:55.407 回答
65

在 MySQL 中,ON DUPLICATE KEY UPDATEINSERT 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] ... ]
于 2009-09-01T09:05:00.660 回答
25

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

  • 主键如果不是代理
  • 列上的唯一约束
  • 多列唯一约束

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

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

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

于 2009-09-01T09:01:01.543 回答
19

尝试以下操作:

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
于 2013-05-10T09:58:48.177 回答
17

这是一个 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'
    )
  );
?>
于 2012-03-06T19:56:38.257 回答
17
REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

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

于 2012-07-06T14:35:08.343 回答
7

UNIQUE如果您有一个可以使用ON DUPLICATE KEY或检查的索引,有几个答案涵盖了如何解决这个问题INSERT IGNORE。情况并非总是如此,并且由于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依赖于 a 的NOT EXISTS,即INSERT仅在数据集不存在时才执行。

于 2016-08-18T18:38:12.850 回答
4

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

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

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

于 2019-09-07T20:53:20.743 回答
4

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

如果您已经有一个唯一键或主键,则使用INSERT INTO ... ON DUPLICATE KEY UPDATE ...or的其他答案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通过子句匹配选择现有行。
  • 将结果与潜在的新行(table s)联合,其中列值是明确给出的(s.id 为 NULL,因此它将生成一个新的自动增量标识符)。
  • 如果找到现有行,则s丢弃 table 中潜在的新行(由于 table 上的 LIMIT 1 t),并且它将始终触发该列的ON DUPLICATE KEY哪个。UPDATEsome_value
  • 如果未找到现有行,则插入潜在的新行(由 table 给出s)。

注意:关系数据库中的每个表都应该至少有一个主自增列id。如果你没有这个,添加它,即使你一开始不需要它。这个“技巧”绝对需要它。

于 2019-12-06T08:26:22.357 回答
-1
INSERT INTO table_name (columns) VALUES (values) ON CONFLICT (id) DO NOTHING;
于 2022-02-21T17:15:16.937 回答