質問
一度に複数の行を挿入できることは知っていますが、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;
解決
はい、可能です。INSERT ... を使用できます。重複キーの更新時。
あなたの例を使用すると:
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);
他のヒント
動的な値があるため、列を更新するには IF または CASE を使用する必要があります。ちょっと見苦しいですが、うまくいくはずです。
あなたの例を使用すると、次のように行うことができます。
UPDATE table SET Col1 = CASE id WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 4 THEN 10 ELSE Col1 END, Col2 = CASE id WHEN 3 THEN 3 WHEN 4 THEN 12 ELSE Col2 END WHERE id IN (1, 2, 3, 4);
質問は古いですが、別の回答でトピックを拡張したいと思います。
私が言いたいのは、これを実現する最も簡単な方法は、複数のクエリをトランザクションでラップすることだけだということです。受け入れられた回答 INSERT ... ON DUPLICATE KEY UPDATE
これは素晴らしいハックですが、その欠点と制限に注意する必要があります。
- 前述のとおり、主キーがテーブルに存在しない行を使用してクエリを起動すると、クエリは新しい「中途半端な」レコードを挿入します。おそらくそれはあなたが望んでいることではありません
- デフォルト値のない null でないフィールドを持つテーブルがあり、クエリでこのフィールドに触れたくない場合は、次のようになります。
"Field 'fieldname' doesn't have a default value"
MySQL では、行を 1 行も挿入しなくても警告が表示されます。厳格に対処して、アプリ内で mysql 警告をランタイム例外に変換すると、問題が発生します。
提案されたバリアントのうち 3 つについていくつかのパフォーマンス テストを行いました。 INSERT ... ON DUPLICATE KEY UPDATE
バリアント、「case / when / then」句を使用したバリアント、およびトランザクションを使用した単純なアプローチ。Python コードと結果を取得できます。 ここ. 。全体的な結論としては、case ステートメントを使用したバリアントは他の 2 つのバリアントよりも 2 倍高速であることが判明しましたが、正しくインジェクションセーフなコードを記述するのは非常に難しいため、私は個人的に最も単純なアプローチに固執しています。トランザクションを使用します。
編集: の調査結果 だくさん 私のパフォーマンス推定が完全に有効ではないことを証明します。参照してください この答え 別のより精緻な研究のために。
別の便利なオプションがまだ言及されていない理由がわかりません:
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;
以下のすべてが InnoDB に適用されます。
3 つの異なる方法の速度を知ることが重要だと感じます。
方法は 3 つあります。
- 入れる:重複キー更新をオンにして挿入
- 取引:トランザクション内の各レコードの更新を行う場所
- 場合:UPDATE 内の異なるレコードごとに、どのケース/いつを実行するか
これをテストしたところ、INSERTメソッドは次のとおりでした。 6.7倍 私にとっては、TRANSACTION メソッドよりも高速です。3,000 行と 30,000 行の両方のセットを試してみました。
TRANSACTION メソッドは、実行中に結果をメモリなどにバッチ処理しますが、クエリを個別に実行する必要があるため、時間がかかります。TRANSACTION メソッドは、レプリケーション ログとクエリ ログの両方でかなりのコストがかかります。
さらに悪いことに、CASE メソッドは 41.1倍 30,000 レコードの INSERT メソッドよりも遅い (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
元に戻す予定がある場合は、最初に。
INSERT メソッドを使用すると auto_increment が増加するという他のコメントを見たことがありますが、それもテストしましたが、そうではないようです。
テストを実行するコードは次のとおりです。また、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).';');
}
UPDATE table1, table2 SET table1.col1='value', table2.col1='value' WHERE table1.col3='567' AND table2.col6='567'
これはうまくいくはずです。
に参考文献があります MySQLマニュアル 複数のテーブルの場合。
一時テーブルを使用する
// 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`");
}
同じテーブルにエイリアスを付けて、挿入する ID を指定できます (行ごとの更新を行う場合:
UPDATE table1 tab1, table1 tab2 -- alias references the same table
SET
col1 = 1
,col2 = 2
. . .
WHERE
tab1.id = tab2.id;
さらに、他のテーブルからも更新できることは明らかです。この場合、更新は「SELECT」ステートメントとしても機能し、指定したテーブルからデータを取得します。クエリで更新値を明示的に指定しているため、2 番目のテーブルは影響を受けません。
なぜ誰も言及しないのか 1 つのクエリ内の複数のステートメント?
phpでは、次を使用します multi_query
mysqliインスタンスのメソッド。
から PHPマニュアル
MySQL では、オプションで 1 つのステートメント文字列に複数のステートメントを含めることができます。複数のステートメントを一度に送信すると、クライアントとサーバーのラウンドトリップが削減されますが、特別な処理が必要になります。
以下は、生の更新 30,000 で他の 3 つの方法と比較した結果です。コードが見つかる ここ これは@Dakusanからの回答に基づいています
取引:5.5194580554962
入れる:0.20669293403625
場合:16.474853992462
マルチ:0.0412278175354
ご覧のとおり、複数のステートメントのクエリは、最高の回答よりも効率的です。
次のようなエラー メッセージが表示された場合:
PHP Warning: Error while sending SET_OPTION packet
増やす必要があるかもしれません max_allowed_packet
私のマシンにあるmysql設定ファイル内 /etc/mysql/my.cnf
そしてmysqldを再起動します。
更新時に結合を使用することにも興味があるかもしれません。これも可能です。
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.
編集:更新する値がデータベース内の他の場所から取得されたものでない場合は、複数の更新クエリを発行する必要があります。
(複数の) インジェクション コマンドを防ぐために実装された 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();
それが役立つことを願っています。
使用
REPLACE INTO`table` VALUES (`id`,`col1`,`col2`) VALUES
(1,6,1),(2,2,3),(3,9,5),(4,16,8);
ご注意ください:
- id は一意の主キーである必要があります
- 外部キーを使用してテーブルを参照する場合、削除を交換してから挿入するため、これによりエラーが発生する可能性があります
以下は 1 つのテーブル内のすべての行を更新します
Update Table Set
Column1 = 'New Value'
次の行では、Column2 の値が 5 を超えるすべての行が更新されます。
Update Table Set
Column1 = 'New Value'
Where
Column2 > 5
全部あるよ アンクウンテック複数のテーブルを更新する例
UPDATE table1, table2 SET
table1.col1 = 'value',
table2.col1 = 'value'
WHERE
table1.col3 = '567'
AND table2.col6='567'
はい。INSERT ON DUPLICATE KEY UPDATE SQL ステートメントを使用して可能です。構文:table_name(a、b、c)値(1,2,3)、(4,5,6)の重複したキーアップデートa = values(a)、b = values(b)、c = values(c)に挿入
PHPを使ってこれを行いました。セミコロンを使用して配列に分割し、ループ経由で送信します。
$con = new mysqli('localhost','user1','password','my_database');
$batchUpdate = true; /*You can choose between batch and single query */
$queryIn_arr = explode(";", $queryIn);
if($batchUpdate) /* My SQL prevents multiple insert*/
{
foreach($queryIn_arr as $qr)
{
if(strlen($qr)>3)
{
//echo '<br>Sending data to SQL1:<br>'.$qr.'</br>';
$result = $conn->query($qr);
}
}
}
else
{
$result = $conn->query($queryIn);
}
$con->close();
UPDATE tableName SET col1='000' WHERE id='3' OR id='5'
これで探しているものが得られるはずです。さらにIDを追加するだけです。テストしてみました。
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 つのクエリで穴テーブルを更新できます