MySQLの複数の更新


388

私はあなたが一度に複数の行を挿入できることを知っています、MySQLで複数の行を一度に(1つのクエリのように)更新する方法はありますか?

編集:たとえば、私は次のようにしています

Name   id  Col1  Col2
Row1   1    6     1
Row2   2    2     3
Row3   3    9     5
Row4   4    16    8

次のすべての更新を1つのクエリに結合したい

UPDATE table SET Col1 = 1 WHERE id = 1;
UPDATE table SET Col1 = 2 WHERE id = 2;
UPDATE table SET Col2 = 3 WHERE id = 3;
UPDATE table SET Col1 = 10 WHERE id = 4;
UPDATE table SET Col2 = 12 WHERE id = 4;

回答:


651

はい、可能です。INSERT... ON DUPLICATE KEY UPDATEを使用できます。

あなたの例を使用して:

INSERT INTO table (id,Col1,Col2) VALUES (1,1,1),(2,2,3),(3,9,3),(4,10,12)
ON DUPLICATE KEY UPDATE Col1=VALUES(Col1),Col2=VALUES(Col2);

22
重複がない場合、その行は挿入されません。idは何をすべきですか?IDのテーブルを保持している別のサイトから情報を取得しているためです。そのIDに関して値を挿入しています。サイトに新しいレコードがある場合、他のすべての情報を除いて、IDとカウントのみを挿入することになります。idのエントリがある場合にのみ、更新する必要があります。それ以外の場合はスキップする必要があります。何をすればよいでしょうか?
ジャヤパルチャンドラン2010

33
注:この回答では、IDが主キーであると想定しています
JM4

11
@JayapalChandranでは、INSERT IGNOREをON DUPLICATE KEY UPDATEと一緒に使用する必要があります。dev.mysql.com/doc/refman/5.5/en/insert.html
Haralan Dobrev

16
@HaralanDobrev INSERT IGNOREを使用しても、重複しないレコードが挿入されます。ジャヤパルは避けたかった。INSERT IGNOREは、すべてのエラーを警告に変換します:( stackoverflow.com/questions/548541/…–
安達武弘

2
この回答は、IDが一意のキーであると想定しています(他の人が言ったように主キーである可能性があります)が、さらに重要なのは、他の一意のキーがないことを想定しています。もしあれば、スパナを投げることができます。
Steve Horvath、2014

129

動的な値があるため、更新する列にはIFまたはCASEを使用する必要があります。少し醜くなりますが、うまくいくはずです。

あなたの例を使用して、あなたはそれを次のようにすることができます:

UPDATEテーブルSET Col1 = CASE id 
                          1時1時 
                          2時2時 
                          4時10時 
                          ELSE Col1 
                        終わり、 
                 列2 =ケースID 
                          3時3時 
                          4時12時 
                          ELSE Col2 
                        終わり
             WHERE id IN(1、2、3、4);

動的更新のために書くのはそれほどきれいではないかもしれませんが、ケーシングの機能を興味深いものにしています...
me_

1
@ user2536953、動的更新にも適しています。例えば、私はPHPでループにその溶液を使用する:$commandTxt = 'UPDATE operations SET chunk_finished = CASE id '; foreach ($blockOperationChecked as $operationID => $operationChecked) $commandTxt .= " WHEN $operationID THEN $operationChecked "; $commandTxt .= 'ELSE id END WHERE id IN ('.implode(', ', array_keys(blockOperationChecked )).');';
BOOLEAN_TYPE

86

質問は古いですが、別の答えでトピックを拡張したいと思います。

私のポイントは、それを達成する最も簡単な方法は、トランザクションで複数のクエリをラップすることです。受け入れられた答えINSERT ... ON DUPLICATE KEY UPDATEは素晴らしいハックですが、その欠点と制限に注意する必要があります。

  • 言われているように、テーブルに主キーが存在しない行でクエリを起動した場合、クエリは新しい「ハーフベイク」レコードを挿入します。おそらくそれはあなたが望むものではありません
  • デフォルト値のないnull以外のフィールドを持つテーブルがあり、クエリでこのフィールドを変更し"Field 'fieldname' doesn't have a default value"たくない場合は、単一の行をまったく挿入しなくても、MySQL警告が表示されます。厳格でmysql警告をアプリの実行時例外に変えると、問題が発生します。

INSERT ... ON DUPLICATE KEY UPDATEバリアント、 "case / when / then"句を含むバリアント、トランザクションを使用した単純なアプローチなど、推奨される3つのバリアントのパフォーマンステストをいくつか行いました。あなたはここで Pythonコードと結果を得るかもしれません。全体的な結論として、caseステートメントを含むバリアントは他の2つのバリアントの2倍の速さであることがわかりますが、そのための正しいインジェクションセーフのコードを書くのは非常に難しいので、私は個人的にトランザクションの使用という最も単純なアプローチに固執します。

編集:Dakusanの調査結果は、私のパフォーマンス推定があまり有効でないことを証明しています。より詳細な別の調査については、この回答を参照しください。


トランザクションを使用する、とても素敵な(そしてシンプルな)ヒント!
mTorres 2014

テーブルがInnoDBタイプでない場合はどうなりますか?
TomeeNS

1
これを行うためのトランザクションへのリンクを誰かが提供できますか?および/またはcaseステートメント付きのバリアントの注入セーフコードのコード?
フランソワM.

1
私はこの投稿でスピードについて与えられた情報を間違っていると思います。以下の投稿でそれについて書いた。stackoverflow.com/questions/3432/multiple-updates-in-mysql/...
Dakusan

1
@Dakusan、素晴らしい答え。私の結果を拡張、コメント、修正してくれてありがとう。
ローマイマンクロフ2016年

72

別の便利なオプションがまだ言及されていない理由がわかりません:

UPDATE my_table m
JOIN (
    SELECT 1 as id, 10 as _col1, 20 as _col2
    UNION ALL
    SELECT 2, 5, 10
    UNION ALL
    SELECT 3, 15, 30
) vals ON m.id = vals.id
SET col1 = _col1, col2 = _col2;

4
これが一番。特に、別のSQLクエリから更新用の値をプルしている場合は、
v010dya 2014

1
これは、大量の列を持つテーブルの更新に最適でした。私はおそらくこのクエリを将来多く使用するでしょう。ありがとう!
Casper Wilkes

このタイプのクエリを試しました。しかし、レコードが30kに達すると、境界サーバーが停止しました。他の解決策はありますか?
Bhavin Chauhan 2016

これは素晴らしいですね。これを、主キーが更新されないが変更する列の識別に使用されるWHERE句と組み合わせてみます。
nl-x

@BhavinChauhan問題を回避するために、join-selectの代わりに一時テーブルを使用してみましたか?
nl-x

41

以下のすべてがInnoDBに適用されます。

3つの異なる方法のスピードを知ることは重要だと感じています。

3つの方法があります。

  1. INSERT:ON DUPLICATE KEY UPDATEを使用したINSERT
  2. トランザクション:トランザクション内の各レコードを更新する場所
  3. ケース:UPDATE内の異なるレコードごとにケース/タイミング

これをテストしたところ、INSERTメソッドは6.7xでした TRANSACTIONメソッドより高速でした。3,000行と30,000行のセットを試しました。

TRANSACTIONメソッドは、個々のクエリを個別に実行する必要がありますが、実行中に結果をメモリなどにバッチ処理しますが、時間がかかります。TRANSACTIONメソッドは、レプリケーションログとクエリログの両方でかなりの負荷がかかります。

さらに悪いことに、CASEメソッドは、30,000レコードのINSERTメソッドより41.1倍遅くなりました(TRANSACTIONより6.1倍遅い)。そして75倍 MyISAMテーブルで遅くなります。INSERTおよびCASEメソッドは、1,000レコードでもブレークしました。100レコードであっても、CASEメソッドの方がかなり高速です。

したがって、一般的に、INSERTメソッドは最も使いやすく、使いやすいと思います。クエリは小さく、読みやすく、アクションのクエリは1つだけです。これはInnoDBとMyISAMの両方に適用されます。

ボーナスのもの:

INSERTの非デフォルトフィールドの問題の解決策は、関連するSQLモードを一時的にオフにすることですSET SESSION sql_mode=REPLACE(REPLACE(@@SESSION.sql_mode,"STRICT_TRANS_TABLES",""),"STRICT_ALL_TABLES","")sql_mode元に戻す予定がある場合は、必ず最初のものを保存してください。

他のコメントについては、auto_incrementがINSERTメソッドを使用して増加すると言っているのを見たことがありますが、これはInnoDBには当てはまるようですが、MyISAMには当てはまりません。

テストを実行するコードは次のとおりです。また、PHPインタープリターのオーバーヘッドを削除するために.SQLファイルを出力します

<?
//Variables
$NumRows=30000;

//These 2 functions need to be filled in
function InitSQL()
{

}
function RunSQLQuery($Q)
{

}

//Run the 3 tests
InitSQL();
for($i=0;$i<3;$i++)
    RunTest($i, $NumRows);

function RunTest($TestNum, $NumRows)
{
    $TheQueries=Array();
    $DoQuery=function($Query) use (&$TheQueries)
    {
        RunSQLQuery($Query);
        $TheQueries[]=$Query;
    };

    $TableName='Test';
    $DoQuery('DROP TABLE IF EXISTS '.$TableName);
    $DoQuery('CREATE TABLE '.$TableName.' (i1 int NOT NULL AUTO_INCREMENT, i2 int NOT NULL, primary key (i1)) ENGINE=InnoDB');
    $DoQuery('INSERT INTO '.$TableName.' (i2) VALUES ('.implode('), (', range(2, $NumRows+1)).')');

    if($TestNum==0)
    {
        $TestName='Transaction';
        $Start=microtime(true);
        $DoQuery('START TRANSACTION');
        for($i=1;$i<=$NumRows;$i++)
            $DoQuery('UPDATE '.$TableName.' SET i2='.(($i+5)*1000).' WHERE i1='.$i);
        $DoQuery('COMMIT');
    }

    if($TestNum==1)
    {
        $TestName='Insert';
        $Query=Array();
        for($i=1;$i<=$NumRows;$i++)
            $Query[]=sprintf("(%d,%d)", $i, (($i+5)*1000));
        $Start=microtime(true);
        $DoQuery('INSERT INTO '.$TableName.' VALUES '.implode(', ', $Query).' ON DUPLICATE KEY UPDATE i2=VALUES(i2)');
    }

    if($TestNum==2)
    {
        $TestName='Case';
        $Query=Array();
        for($i=1;$i<=$NumRows;$i++)
            $Query[]=sprintf('WHEN %d THEN %d', $i, (($i+5)*1000));
        $Start=microtime(true);
        $DoQuery("UPDATE $TableName SET i2=CASE i1\n".implode("\n", $Query)."\nEND\nWHERE i1 IN (".implode(',', range(1, $NumRows)).')');
    }

    print "$TestName: ".(microtime(true)-$Start)."<br>\n";

    file_put_contents("./$TestName.sql", implode(";\n", $TheQueries).';');
}

1
あなたはここで主の働きをしている;)とても感謝しています。
チリ

MariaLで4万行を使用してGoLangとPHPの間のパフォーマンスをテストしたところ、PHPで2秒、golangで6秒以上かかっていました。SO、パフォーマンスを向上させる方法について疑問に思い始めています... INSERT ... ON DUPLICATE KEY UPDATEを使用しています... Golangで0.74秒、PHPで0.86秒を取得しました!!!!
Diego Favero

1
私のコードのポイントは、タイミング結果を厳密にSQLステートメントに制限することであり、言語やライブラリのコードではありません。GoLangとPHPは2つの完全に異なる言語であり、完全に異なるものを対象としています。PHPは、ほとんど制限されたパッシブガベージコレクションを備えたシングルスレッドでのシングルランスクリプト環境向けです。GoLangは、主な言語機能の1つとして積極的なガベージコレクションとマルチスレッドを備えた、長時間実行されるコンパイル済みアプリケーション向けです。言語の機能性と理由の点で、ほとんど異なることはありません。[続く]
Dakusan

したがって、テストを実行するときは、速度測定を厳密にSQLステートメントの「クエリ」関数呼び出しに制限してください。厳密にクエリ呼び出しではないソースコードの他の部分を比較して最適化することは、リンゴとオレンジを比較するようなものです。結果をこれに制限する場合(文字列がプリコンパイルされ、準備ができている)、結果は非常に似ているはずです。その時点での違いは、言語のSQLライブラリの障害であり、必ずしも言語自体の障害ではありません。私の意見では、INSERT ON DUPLICATEのソリューションでしたし、常に最良の選択肢となります[続き]。
Dakusan

GoLangの方が速いというコメントについては、これらの言語とそのデザインの多数の注意点やニュアンスを考慮に入れていない、非常に幅広いステートメントです。Javaはインタープリター型言語ですが、15年前に特定のシナリオでCにほぼ匹敵する(場合によってはCに匹敵する)ことがわかりました。また、Cはコンパイルされた言語であり、アセンブラーに加えて、最も一般的な最下位レベルのシステム言語です。GoLangの仕事が大好きで、最も一般的なシステムの1つになり、最適化される力と流動性があることは間違いありません。[
続き

9

一時テーブルを使用する

// Reorder items
function update_items_tempdb(&$items)
{
    shuffle($items);
    $table_name = uniqid('tmp_test_');
    $sql = "CREATE TEMPORARY TABLE `$table_name` ("
        ."  `id` int(10) unsigned NOT NULL AUTO_INCREMENT"
        .", `position` int(10) unsigned NOT NULL"
        .", PRIMARY KEY (`id`)"
        .") ENGINE = MEMORY";
    query($sql);
    $i = 0;
    $sql = '';
    foreach ($items as &$item)
    {
        $item->position = $i++;
        $sql .= ($sql ? ', ' : '')."({$item->id}, {$item->position})";
    }
    if ($sql)
    {
        query("INSERT INTO `$table_name` (id, position) VALUES $sql");
        $sql = "UPDATE `test`, `$table_name` SET `test`.position = `$table_name`.position"
            ." WHERE `$table_name`.id = `test`.id";
        query($sql);
    }
    query("DROP TABLE `$table_name`");
}


6

なぜ1つのクエリで複数のステートメントに言及しないのですか?

phpではmulti_query、mysqliインスタンスのメソッドを使用しています。

PHPマニュアルから

MySQLでは、オプションで1つのステートメント文字列に複数のステートメントを含めることができます。複数のステートメントを一度に送信すると、クライアントとサーバーのラウンドトリップが減少しますが、特別な処理が必要になります。

これは、更新30,000 rawの他の3つの方法と比較した結果です。@Dakusanからの回答に基づくコードはここにあります

トランザクション:5.5194580554962
挿入:0.20669293403625
ケース:16.474853992462
マルチ:0.0412278175354

ご覧のとおり、複数のステートメントのクエリは、最高の回答よりも効率的です。

次のようなエラーメッセージが表示された場合:

PHP Warning:  Error while sending SET_OPTION packet

max_allowed_packet私のマシンにあるmysql configファイルを増やしてから、/etc/mysql/my.cnfmysqldを再起動する必要があるかもしれません。


以下のすべての比較は、INSERTテストに対して実行されます。同じ条件でテストを実行したところ、トランザクションがない場合、300行では145倍遅くなり、3000行では753倍遅くなりました。最初は3万行から始めていましたが、自分で昼食を作って帰って来たのです。これは、個々のクエリを実行し、それぞれを個別にデータベースにフラッシュすることは途方もなく高価であるため、理にかなっています。特に複製に関して。ただし、トランザクションをオンにすることには大きな違いがあります。3,000行では1.5倍、30,000行では2.34 かかりました。[続く]
Dakusan

しかし、あなたはそれが(トランザクションで)高速であることについては正しかった。3,000行と30,000行の両方で、INSERTメソッド以外のすべてよりも高速でした。特別なMySQL API呼び出しでバッチ処理されている場合でも、30,000クエリよりも1クエリを実行した方が良い結果を得る方法は絶対にありません。300行しか実行しなかったため、他のすべてのメソッド(驚いたことに)よりもはるかに高速で、CASEメソッドとほぼ同じグラフ曲線に従いました。これが高速であることは、2つの方法で説明できます。1つ目は、 "ON DUPLICATE KEY [続き]]により、INSERTメソッドは基本的に常に2行を挿入するということです
Dakusan

UPDATE "は" INSERT "と" UPDATE "の両方を引き起こします。もう1つは、SQLプロセッサでは、インデックスのルックアップのために一度に1行しか編集できないため、作業量が少なくなることです。しかし、あなたの追加試験は、固体に見える私も必ず複製がこのコールを処理する方法を、実際にはないよ。これは、UPDATE呼び出しを行うためだけに働くだろう挿入呼び出しは常に単一のINSERTクエリを最速になります。。。。
Dakusan

41秒かかったforループ内のエラーを修正するために、テーブルで一度に300回のUPDATEを実行していました。同じUPDATEクエリを1つ$mysqli->multi_query($sql)に入れるのに "0"秒かかりました。ただし、後続のクエリが失敗したため、これを別の「プログラム」にしました。
Chris K

ありがとう。マルチクエリを使用して、1分間で約5k行を更新できました(これ以上テストしませんでした)。誰かがPDOソリューションを探している場合:stackoverflow.com/questions/6346674/…–
Scofield

3

(複数の)インジェクションコマンドを防止するために実装されたMySQLの「安全メカニズム」を無効にする「マルチステートメント」と呼ばれる変更可能な設定があります。MySQLの「素晴らしい」実装に典型的なものであり、ユーザーが効率的なクエリを実行することもできません。

ここ(http://dev.mysql.com/doc/refman/5.1/en/mysql-set-server-option.html)は、設定のC実装に関する情報です。

PHPを使用している場合は、mysqliを使用して複数のステートメントを実行できます(phpはしばらくの間mysqliに同梱されていると思います)

$con = new mysqli('localhost','user1','password','my_database');
$query = "Update MyTable SET col1='some value' WHERE id=1 LIMIT 1;";
$query .= "UPDATE MyTable SET col1='other value' WHERE id=2 LIMIT 1;";
//etc
$con->multi_query($query);
$con->close();

お役に立てば幸いです。


4
これは、クエリを個別に送信するのと同じです。唯一の違いは、すべてを1つのネットワークパケットで送信することですが、UPDATEは引き続き個別のクエリとして処理されます。それらを1つのトランザクションでラップすると、変更が一度にテーブルにコミットされます。
Marki555 14

3
それらを1つのトランザクションでラップする方法は?見せてください。
TomeeNS 2014

@TomeeNS mysqli::begin_transaction(..)クエリの送信前と送信mysql::commit(..)後に使用します。または、クエリ自体のSTART TRANSACTION最初とCOMMIT最後のステートメントとして使用します。
JuhaPalomäki2016年

3

同じテーブルにエイリアスを設定して、挿入したいIDを取得できます(行ごとの更新を行う場合:

UPDATE table1 tab1, table1 tab2 -- alias references the same table
SET 
col1 = 1
,col2 = 2
. . . 
WHERE 
tab1.id = tab2.id;

さらに、他のテーブルからも更新できることは明らかなはずです。この場合、更新は「SELECT」ステートメントとしても機能し、指定したテーブルのデータを提供します。クエリで更新値を明示的に指定しているため、2番目のテーブルは影響を受けません。


2

また、更新で結合を使用することもできますが、これも可能です。

Update someTable Set someValue = 4 From someTable s Inner Join anotherTable a on s.id = a.id Where a.id = 4
-- Only updates someValue in someTable who has a foreign key on anotherTable with a value of 4.

編集:更新する値がデータベースの他の場所から取得されていない場合は、複数の更新クエリを発行する必要があります。


1

そして今、簡単な方法

update speed m,
    (select 1 as id, 20 as speed union
     select 2 as id, 30 as speed union
     select 99 as id, 10 as speed
        ) t
set m.speed = t.speed where t.id=m.id

-1

使用する

REPLACE INTO`table` VALUES (`id`,`col1`,`col2`) VALUES
(1,6,1),(2,2,3),(3,9,5),(4,16,8);

ご注意ください:

  • idは一意の主キーである必要があります
  • 外部キーを使用してテーブルを参照すると、REPLACEは削除してから挿入するため、エラーが発生する可能性があります。

-3

以下は1つのテーブルのすべての行を更新します

Update Table Set
Column1 = 'New Value'

次のものは、Column2の値が5より大きいすべての行を更新します

Update Table Set
Column1 = 'New Value'
Where
Column2 > 5

複数のテーブルを更新するすべてのUnkwntechの例があります

UPDATE table1, table2 SET
table1.col1 = 'value',
table2.col1 = 'value'
WHERE
table1.col3 = '567'
AND table2.col6='567'

-3

はい..それは、INSERT ON DUPLICATE KEY UPDATE SQLステートメントを使用して可能です。構文:INSERT INTO table_name(a、b、c)VALUES(1,2,3)、(4,5,6)ON DUPLICATE KEY UPDATE a = VALUES(a)、b = VALUES(b)、c = VALUES(c)


-5
UPDATE tableName SET col1='000' WHERE id='3' OR id='5'

これはあなたが探しているものを達成するはずです。さらにIDを追加してください。私はそれをテストしました。


-7
UPDATE `your_table` SET 

`something` = IF(`id`="1","new_value1",`something`), `smth2` = IF(`id`="1", "nv1",`smth2`),
`something` = IF(`id`="2","new_value2",`something`), `smth2` = IF(`id`="2", "nv2",`smth2`),
`something` = IF(`id`="4","new_value3",`something`), `smth2` = IF(`id`="4", "nv3",`smth2`),
`something` = IF(`id`="6","new_value4",`something`), `smth2` = IF(`id`="6", "nv4",`smth2`),
`something` = IF(`id`="3","new_value5",`something`), `smth2` = IF(`id`="3", "nv5",`smth2`),
`something` = IF(`id`="5","new_value6",`something`), `smth2` = IF(`id`="5", "nv6",`smth2`) 

//次のようにphpでビルドするだけです

$q = 'UPDATE `your_table` SET ';

foreach($data as $dat){

  $q .= '

       `something` = IF(`id`="'.$dat->id.'","'.$dat->value.'",`something`), 
       `smth2` = IF(`id`="'.$dat->id.'", "'.$dat->value2.'",`smth2`),';

}

$q = substr($q,0,-1);

したがって、1つのクエリで穴テーブルを更新できます


私は反対票を投じなかったが、それが必要でないとき(そしてあなたがまだそれをやっているとき、あなたがに設定somethingしているときsomething)に
反対
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.