我从谷歌搜索开始,发现这篇文章讨论了互斥表。
我有一张约有1400万条记录的表。如果我想以相同的格式添加更多数据,是否有一种方法可以确保我要插入的记录在不使用一对查询的情况下就不存在(即,一个查询要检查,一个查询要插入的结果集是空)?
如果unique
字段上存在约束,是否可以保证该约束insert
将失败?
似乎只有一个约束,当我通过php发出插入命令时,脚本就发出了嘶哑的声音。
我从谷歌搜索开始,发现这篇文章讨论了互斥表。
我有一张约有1400万条记录的表。如果我想以相同的格式添加更多数据,是否有一种方法可以确保我要插入的记录在不使用一对查询的情况下就不存在(即,一个查询要检查,一个查询要插入的结果集是空)?
如果unique
字段上存在约束,是否可以保证该约束insert
将失败?
似乎只有一个约束,当我通过php发出插入命令时,脚本就发出了嘶哑的声音。
Answers:
采用 INSERT IGNORE INTO table
见http://bogdan.org.ua/2007/10/18/mysql-insert-if-not-exists-syntax.html
还有INSERT … ON DUPLICATE KEY UPDATE
语法,您可以在dev.mysql.com上找到说明
根据Google的webcache从bogdan.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:使用REPLACE
很简单:
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查询的文章。
INSERT … ON DUPLICATE KEY UPDATE
更好,因为它不删除行,保留任何auto_increment
列和其他数据。
INSERT … ON DUPLICATE KEY UPDATE
方法不会在插入失败的情况下增加任何AUTO_INCREMENT列。可能是因为它并不是真的失败,而是UPDATE。
解:
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`)
插入数据(如果中间查询返回了任何数据)。
INSERT IGNORE
和INSERT ON DUPLICATE KEY
需要唯一键约束)
stuff for value1
和stuff for value2
相同怎么办?这将引发Duplicate column name
SELECT 1
而不是SELECT *
在子查询中。索引可以满足这一要求的可能性更大。
基于mysql.com的重复键更新更新的示例
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 [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] ... ]
如果可以接受异常,则任何简单的约束都可以胜任。例子 :
抱歉,这看似简单。我知道您与我们分享的链接看起来很糟糕。;-(
但我毫不留情地给出这个答案,因为它似乎可以满足您的需求。(否则,这可能会触发您更新您的要求,这也将是“一件好事”(TM))。
编辑:如果插入操作将打破数据库唯一约束,则驱动程序将在数据库级别引发异常。它肯定会停止您的脚本,但会失败。在PHP中一定有可能解决这种情况...
INSERT IGNORE
基本上将所有错误都转换为警告,以便您的脚本不会中断。然后,您可以使用命令查看任何警告SHOW WARNINGS
。还有一个重要说明:UNIQUE约束不适用于NULL值。row1(1,NULL)和row2(1,NULL)都将被插入(除非打破了另一个约束,例如主键)。不幸的。
这是一个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时的更多帮助。
REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;
如果记录存在,它将被覆盖;如果尚不存在,将创建它。
REPLACE
可能会删除该行,然后插入而不是更新。副作用是约束可能会删除其他对象并触发删除触发器。
尝试以下方法:
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
如果您有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
取决于的NOT EXISTS
,即INSERT
仅在数据集不存在时才执行。
值得注意的是,无论语句是否成功,INSERT IGNORE仍将像普通的INSERT一样增加主键。
这将导致您的主键出现间隙,这可能会使程序员的心理不稳定。或者,如果您的应用程序设计不佳且依赖完美的增量主键,则可能会令人头疼。
调查一下innodb_autoinc_lock_mode = 0
(服务器设置,并且会带来轻微的性能下降),或者先使用SELECT来确保您的查询不会失败(这也会带来性能下降和额外的代码)。
SELECT
失败开始,整个目的就是交出一大笔INSERT
s,而不用担心重复。
如果您已经具有唯一键或主键,则其他答案都带有INSERT INTO ... ON DUPLICATE KEY UPDATE ...
或REPLACE INTO ...
应该很好用(请注意,如果存在则替换为deletes,然后插入-因此不会部分更新现有值)。
但是,如果你有值some_column_id
和some_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
被丢弃(由于table的LIMIT 1 t
),它将始终触发将ON DUPLICATE KEY
其UPDATE
作为该some_value
列的行。s
)。注意:关系数据库中的每个表都应至少具有一个主自动增量id
列。如果您没有此功能,请添加它,即使您一眼不需要时也可以添加它。绝对需要此“技巧”。
INSERT INTO ... SELECT FROM
格式。你怎么还
INSERT INTO... SELECT FROM...
解决方案。请为我提供指向相同答案的链接,如果找到它,我将删除此答案,否则,您将投票给我(交易吗?)。确保验证要链接的答案仅使用1个查询(针对update + insert),不执行任何事务,并且能够定位已知唯一的列的任意组合(因此,单独的列不会需要是唯一的)。