关于php:如何在MySQL中“插入,如果不存在”?

How to 'insert if not exists' in MySQL?

我从谷歌开始,找到了这篇关于互斥表的文章。

我有一张有1400万张唱片的桌子。如果我想以相同的格式添加更多的数据,是否有一种方法可以确保我要插入的记录在不使用一对查询的情况下不存在(即,一个要检查的查询和一个要插入的查询是空的结果集)?

如果一个字段上的unique约束已经存在,那么它是否保证insert将失败?

似乎只要有一个约束,当我通过php发布insert时,脚本就会发出吱吱声。


使用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上找到解释。

从bogdan.org.ua发帖,根据谷歌的网络缓存:

18th October 2007

To start: as of the latest MySQL, syntax presented in the title is not
possible. But there are several very easy ways to accomplish what is
expected using existing functionality.

There are 3 possible solutions: using INSERT IGNORE, REPLACE, or
INSERT … ON DUPLICATE KEY UPDATE.

Imagine we have a table:

1
2
3
4
5
6
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;

Now imagine that we have an automatic pipeline importing transcripts
meta-data from Ensembl, and that due to various reasons the pipeline
might be broken at any step of execution. Thus, we need to ensure two
things: 1) repeated executions of the pipeline will not destroy our
database, and 2) repeated executions will not die due to ‘duplicate
primary key’ errors.

Method 1: using REPLACE

It’s very simple:

1
2
3
4
REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

If the record exists, it will be overwritten; if it does not yet
exist, it will be created. However, using this method isn’t efficient
for our case: we do not need to overwrite existing records, it’s fine
just to skip them.

Method 2: using INSERT IGNORE Also very simple:

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

Here, if the ‘ensembl_transcript_id’ is already present in the
database, it will be silently skipped (ignored). (To be more precise,
here’s a quote from MySQL reference manual:"If you use the IGNORE
keyword, errors that occur while executing the INSERT statement are
treated as warnings instead. For example, without IGNORE, a row that
duplicates an existing UNIQUE index or PRIMARY KEY value in the table
causes a duplicate-key error and the statement is aborted.".) If the
record doesn’t yet exist, it will be created.

This second method has several potential weaknesses, including
non-abortion of the query in case any other problem occurs (see the
manual). Thus it should be used if previously tested without the
IGNORE keyword.

There is one more option: to use INSERT … ON DUPLICATE KEY UPDATE
syntax, and in the UPDATE part just do nothing do some meaningless
(empty) operation, like calculating 0+0 (Geoffray suggests doing the
id=id assignment for the MySQL optimization engine to ignore this
operation). Advantage of this method is that it only ignores duplicate
key events, and still aborts on other errors.

As a final notice: this post was inspired by Xaprb. I’d also advise to
consult his other post on writing flexible SQL queries.


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

或者,外部SELECT语句可以引用DUAL来处理表最初为空的情况:

1
2
3
4
5
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


对于重复的密钥更新,或者insert-ignore可以是MySQL的可行解决方案。

基于mysql.com的重复密钥更新示例

1
2
3
4
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示例

1
2
3
4
5
6
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] ... ]

或:

1
2
3
4
5
6
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] ... ]

或:

1
2
3
4
5
6
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    SELECT ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]


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

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

对不起,这看起来很简单。我知道你和我们分享的链接看起来很糟糕。;

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

编辑:如果插入会破坏数据库唯一约束,则会在数据库级别引发异常,由驱动程序中继。它肯定会以失败停止您的脚本。一定有可能在PHP中解决这个问题…


1
2
3
4
REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

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


这里有一个PHP函数,它只在表中不存在所有指定列值的情况下插入一行。

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

  • 如果表为空,则将添加行。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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;
    }

示例用法:

1
2
3
4
5
6
7
8
<?php
insert_unique('mytable', array(
  'mycolumn1' => 'myvalue1',
  'mycolumn2' => 'myvalue2',
  'mycolumn3' => 'myvalue3'
  )
);
?>


请尝试以下操作:

1
2
3
4
5
6
7
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


如果你有一个可以与ON DUPLICATE KEYINSERT IGNORE对比的UNIQUE指数,那么有几个答案可以涵盖如何解决这个问题。这并不总是如此,而且由于UNIQUE有一个长度约束(1000字节),您可能无法更改它。例如,我必须在WordPress中使用元数据(wp_postmeta)。

我最终通过两个问题解决了它:

1
2
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查询,当所讨论的数据集不存在时,查询1无效。查询2是依赖于NOT EXISTSINSERT,即只有当数据集不存在时才执行INSERT


尝试:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Check if exist cod = 56789
include"database.php";

$querycheck = mysql_query ("SELECT * FROM `YOURTABLE` WHERE `xxx` = '56789';");
$countrows = mysql_num_rows($querycheck);
if($countrows == '1')
{
  // Exist
}
else
{
 // .... Not exist
}

或者你可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Check if exist cod = 56789
include"database.php";

$querycheck = mysql_query ("SELECT * FROM `YOURTABLE` WHERE `xxx` = '56789';");
$countrows = mysql_num_rows($querycheck);
while($result = mysql_fetch_array($querycheck))
{
    $xxx = $result['xxx'];
    if($xxx == '56789')
    {
      // Exist
    }
    else
    {
      // Not exist
    }
}

这种方法简便快捷。为了提高大表索引列"xxx"中查询的速度(在我的示例中)。