MySQLに「存在しない場合は挿入する」方法は?


838

私はグーグルで始め、mutexテーブルについて説明しているこの記事を見つけました。

約1,400万レコードのテーブルがあります。同じ形式でさらにデータを追加したい場合、クエリのペアを使用せずに、挿入したいレコードが存在しないことを確認する方法があります(つまり、チェックするクエリと挿入するクエリが結果セットである)空の)?

uniqueフィールド保障上の制約は、insertそれがすでにあるかどう失敗しますか?

制約があるだけで、php経由でinsertを発行すると、スクリプトが鳴るようです。



auto_inc値を書き込まないことについては、stackoverflow.com / questions / 44550788 /…を参照してください。
リックジェームズ

@RickJames - :)面白いのq ..ですが、必ずそれが直接このQに近い関連ではない
ウォーレン

1
それはコメントで言及されており、他の質問はこの質問は「完全な複製」であると主張しました。ですから、他の人の利益のために質問をリンクすることは良い考えだと感じました。
リックジェームズ

1
ああ、私はサイドバーを見るとは思わない。
リックジェームズ

回答:


807

使用する 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つの可能な解決策があります。INSERTIGNORE、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からトランスクリプトメタデータをインポートする自動パイプラインがあり、さまざまな理由によりパイプラインが実行の任意のステップで壊れている可能性があると想像してください。したがって、2つのことを確認する必要があります。

  1. パイプラインを繰り返し実行してもデータベースは破壊されません

  2. 「主キーの重複」エラーが原因で繰り返し実行が停止することはありません。

方法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値が原因で重複キーエラーが発生し、ステートメントが中止されます。」))レコードがまだ存在しない場合は、作成されます。

この2番目の方法には、他の問題が発生した場合にクエリが中断しないことなど、いくつかの潜在的な弱点があります(マニュアルを参照)。したがって、以前にIGNOREキーワードなしでテストした場合は、これを使用する必要があります。

方法3:重複キー更新でINSERT…を使用する:

3番目のオプションはINSERT … ON DUPLICATE KEY UPDATE 構文を使用することです。UPDATE部分では、0 + 0の計算など、意味のない(空の)操作を何も実行しません(Geoffrayは、MySQL最適化エンジンがこの操作を無視するようにid = id割り当てを行うことを提案しています)。この方法の利点は、重複するキーイベントのみが無視され、他のエラーが発生しても中止されることです。

最後のお知らせとして:この投稿はXaprbに触発されました。また、柔軟なSQLクエリの記述に関する彼の他の投稿を参照することをお勧めします。


3
それを「遅延」と組み合わせてスクリプトを高速化できますか?
ウォーレン

3
はい、遅延挿入はあなたのために物事をスピードアップするかもしれません。試してみる
knittl


10
INSERT … ON DUPLICATE KEY UPDATE行は削除されず、auto_increment列やその他のデータは保持されるため、より優れています。
しのばせる

15
みんなに知らせるためだけに。INSERT … ON DUPLICATE KEY UPDATEメソッドを使用すると、挿入が失敗したAUTO_INCREMENT列がインクリメントされます。おそらくそれは実際には失敗していないが、更新されたからでしょう。
not2qubit 2013年

216

解決:

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 として使用すると、挿入するデータを含む行がすでに存在するかどうかが検出されます。この種類の行が1つ見つかった後、クエリが停止する可能性があるため、LIMIT 1(マイクロ最適化は省略できます)。

中間クエリ

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

挿入する値を表します。DUALすべてのOracleデータベースにデフォルトで存在する特別な1行1列のテーブルを指します(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`) 

中間クエリによってデータが返された場合は、データを挿入します。


4
これを使用する方法について、もう少し情報を提供できますか?
Alex V

36
このバリアントは、テーブルに一意のキーが存在しない場合(INSERT IGNOREおよびINSERT ON DUPLICATE KEY一意のキー制約が必要な場合)に適しています
rabudde 2013

2
「from table」の代わりに2行目で「from dual」を使用する場合、「limit 1」句は必要ありません。
リッチ

6
同じ場合はどうstuff for value1なりstuff for value2ますか?これはDuplicate column name
Robin

1
サブクエリのSELECT 1代わりに私も多くを好みSELECT *ます。これはインデックスで満たすことができる可能性がはるかに高くなります。
Arth

58

重複キーの更新時、または挿入無視は、MySQLで実行可能なソリューションになります。


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] ... ]

24

例外が許容できる場合は、簡単な制約で十分です。例:

  • 代理でない場合の主キー
  • 列の一意制約
  • 複数列の一意制約

申し訳ありませんが、これは一見単純そうです。あなたが私たちと共有するリンクに直面してそれが悪いように見えるのを知っています。;-(

しかし、あなたのニーズを満たしているように見えるので、私は決してこの答えを出しません。(そうでない場合は、要件の更新をトリガーする可能性があり、これも「良いこと」(TM)になります)。

編集:挿入によってデータベースの一意の制約が破られる場合、データベースレベルで例外がスローされ、ドライバーによってリレーされます。失敗すると、スクリプトは確実に停止します。PHPではそのような場合に対処することが可能でなければなりません...


1
私は質問に説明を追加しました-あなたの答えはまだ適用されますか?
ウォーレン

2
そうだと思います。一意制約により、正しくない挿入が失敗します。注:コードでこの失敗に対処する必要がありますが、これは非常に標準的なことです。
KLE、

1
とりあえず、私が受け入れた解決策に固執するつもりですが、アプリの成長に合わせてINSERTの失敗などを処理する方法を詳しく見ていきます
warren

3
INSERT IGNORE基本的にすべてのエラーを警告に変更して、スクリプトが中断されないようにします。次に、コマンドを使用して警告を表示できますSHOW WARNINGS。もう1つの重要な注意:UNIQUE制約はNULL値では機能しません。row1(1、NULL)とrow2(1、NULL)の両方が挿入されます(主キーなどの別の制約が壊れている場合を除く)。残念です。
サイモンイースト

18

次のPHP関数は、指定されたすべての列の値がテーブルに存在しない場合にのみ行を挿入します。

  • 列の1つが異なる場合、行が追加されます。

  • テーブルが空の場合、行が追加されます。

  • 指定されたすべての列が指定された値を持つ行が存在する場合、その行は追加されません。

    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'
  )
);
?>

5
挿入の負荷が非常に大きい場合は、かなり高価です。
ЭџadДьdulяңмaи

正しいが、特定の検査を追加する必要がある場合は効率的
Charles Forest

1
警告: mysql_*拡張機能はPHP 5.5.0で非推奨になり、PHP 7.0.0で削除されました。代わりに、mysqliまたはPDO_MySQL拡張機能を使用する必要があります。MySQL APIを選択する際のヘルプについては、MySQL APIの概要もご覧ください。
ダーマン

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

レコードが存在する場合は上書きされます。まだ存在しない場合は作成されます。


10
REPLACE行を削除してから、更新ではなく挿入する場合があります。副作用として、制約により他のオブジェクトが削除され、削除トリガーが発生する場合があります。
xmedeko 2017

1
MySQLマニュアルから:「REPLACEは、テーブルにPRIMARY KEYまたはUNIQUEインデックスがある場合にのみ意味があります。それ以外の場合、新しい行が別の行を複製するかどうかを判断するために使用されるインデックスがないため、それはINSERTと同等になります。」
BurninLeo

16

以下を試してください:

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

5
試してみてください OPや何千人もの将来の研究者を教育することはほとんどないので、この回答はStackOverflowでは価値がありません。この回答を編集して、ソリューションがどのように機能するか、なぜそれが良いアイデアであるかを含めてください。
mickmackusa

1
一致するフィールドがキーでない場合の完璧なソリューション..!
レオ

6

またはでUNIQUEチェックできるインデックスがある場合、これを解決する方法をカバーするいくつかの回答があります。これは常に当てはまるわけではなく、長さの制約(1000バイト)があるため、変更できない場合があります。たとえば、WordPress()でメタデータを操作する必要がありました。ON DUPLICATE KEYINSERT IGNOREUNIQUEwp_postmeta

私は最後に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クエリで、問題のデータセットが存在しない場合は効果がありません。クエリ2はINSERTに依存するクエリですNOT EXISTS。つまりINSERT、データセットが存在しない場合にのみ実行されます。


2

注目に値するのは、通常のINSERTのようにステートメントが成功したかどうかにかかわらず、INSERT IGNOREは依然として主キーを増分するということです。

これにより、主キーにギャップが生じ、プログラマーが精神的に不安定になる可能性があります。または、アプリケーションの設計が不十分で、完全な増分主キーに依存している場合は、頭痛の種になる可能性があります。

見てinnodb_autoinc_lock_mode = 0(サーバーの設定は、とわずかなパフォーマンスヒットが付属しています)、または必ずあなたのクエリは(もパフォーマンスヒットと余分なコードが付属していた)失敗することはありません作るために最初のSELECTを使用します。


なぜ「主キーのギャップ」-場合によっては-「プログラマーを精神的に不安定にする」のでしょうか?たとえば、レコードを削除するたびに、主キーでギャップが常に発生します。
ウォーレン

最初は、sのSELECT大きなバッチを渡すだけでINSERT、重複を心配したくないという目的をすべて打ち破ります。
ウォーレン

2

既知の主キーなしで更新または挿入

既に一意キーまたは主キーがある場合、他のいずれかで答えるか、INSERT INTO ... ON DUPLICATE KEY UPDATE ...またはREPLACE INTO ...正常に機能するはずです(存在する場合は削除に置き換えてから挿入するため、既存の値が部分的に更新されないことに注意してください)。

ただし、some_column_idおよびの値がある場合some_type、その組み合わせは一意であることがわかっています。そしてsome_value、存在する場合は更新し、存在しない場合は挿入します。そして、(トランザクションの使用を回避するために)たった1つのクエリで実行したいとします。これは解決策かもしれません:

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により)tableからの潜在的な新しい行が破棄されt、常にその列ON DUPLICATE KEYをトリガーUPDATEsome_valueます。
  • 既存の行が見つからない場合、潜在的な新しい行が挿入されます(tableで指定されているとおりs)。

注:リレーショナルデータベースのすべてのテーブルには、少なくともプライマリ自動インクリメントid列が必要です。これがない場合は、一目で必要ない場合でも追加してください。この「トリック」には間違いなく必要です。


他のいくつかの回答者がINSERT INTO ... SELECT FROMフォーマットを提案しています。どうしてあなたも?
ウォーレン

2
@warrenあなたは私の答えを読んでいないか、あなたがそれを理解していないか、または私はそれを適切に説明していませんでした。いずれにせよ、以下を強調させてください。これは単なる通常のINSERT INTO... SELECT FROM...解決策ではありません。同じ回答へのリンクを参照してください。見つけられた場合は、この回答を削除します。それ以外の場合は、私の回答に賛成します(取引?)。リンクしようとしている回答が1つのクエリ(更新+挿入用)のみを使用し、トランザクションを使用していないこと、および一意であることがわかっている列の任意の組み合わせをターゲットにできることを確認してください。一意である必要があります)。
イエティ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.