MySQL中的多个更新
我知道你可以一次插入多行,有没有办法一次更新多行(如在一个查询中)在MySQL中?
编辑:例如,我有以下
Name id Col1 Col2 Row1 1 6 1 Row2 2 2 3 Row3 3 9 5 Row4 4 16 8
我想将所有以下更新结合到一个查询中
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 … 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);
由于您具有dynamic值,因此需要使用IF或CASE来更新列。 它变得有点丑,但它应该工作。
用你的例子,你可以这样做:
更新表SET SET Col1 = CASE ID 当1时1 当2时2 那时4日10 ELSE Col1 结束, Col2 = CASE ID 那时3日3 当4日12 ELSE Col2 结束 在哪里(1,2,3,4);
这个问题很老,但我想用另一个答案来扩展这个话题。
我的意思是,实现它的最简单的方法就是用事务包装多个查询。 被接受的答案INSERT ... ON DUPLICATE KEY UPDATE
是一个不错的破解,但是应该意识到它的缺点和限制:
- 如上所述,如果您碰巧使用表中不存在主键的行启动查询,则该查询将插入新的“半成品”logging。 可能这不是你想要的
- 如果你有一个没有默认值的非空字段的表,并且不希望在查询中触及这个字段,你会得到
"Field 'fieldname' doesn't have a default value"
即使你"Field 'fieldname' doesn't have a default value"
MySQL警告'不要插入一行。 它会让你陷入麻烦,如果你决定是严格的,并将mysql警告转变为你的应用程序的运行时exception。
我对三种build议变体进行了一些性能testing,其中包括INSERT ... ON DUPLICATE KEY UPDATE
变体,带有“case / when / then”子句的变体以及带有事务的幼稚方法。 你可能会在这里得到Python代码和结果。 总体的结论是,带有case语句的变体是两个其他变体的两倍,但是为它编写正确和注入安全的代码是相当困难的,所以我个人坚持最简单的方法:使用事务。
编辑: Dakusan的调查结果certificate,我的performance估计不是很有效。 请看这个答案进行另一个更详细的研究。
不知道为什么还没有提到另一个有用的选项:
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。
我觉得三种不同方法的速度是重要的。
有三种方法:
- INSERT:使用ON DUPLICATE KEY UPDATE进行插入
- 交易:您在哪里为交易中的每条logging进行更新
- CASE:在哪一种情况下/何时UPDATE中的每个不同的logging
我只是testing了这一点,INSERT方法比TRANSACTION方法快了6.7倍 。 我尝试了一组3000和30000行。
TRANSACTION方法仍然需要运行每个单独的查询,这需要花费时间,虽然它在执行时将结果批量存储在内存中。 在复制和查询日志中,TRANSACTION方法也相当昂贵。
更糟糕的是,CASE方法的速度比INSERT方法的速度慢31,000倍(比TRANSACTION慢6.1倍)。 在MyISAM中速度要慢75倍 。 INSERT和CASE方法在大约1,000条logging中都破解了。 即使在100条logging中,CASE方法也比较快。
所以一般来说,我觉得INSERT方法是最好的,最容易使用。 查询更小,更容易阅读,只需要处理1个查询。 这适用于InnoDB和MyISAM。
奖金的东西:
INSERT非默认字段问题的解决scheme是暂时closures相关的SQL模式: SET SESSION sql_mode=REPLACE(REPLACE(@@SESSION.sql_mode,"STRICT_TRANS_TABLES",""),"STRICT_ALL_TABLES","")
。 如果您计划恢复sql_mode
请确保先保存sql_mode
。
至于其他评论,我已经看到,说auto_increment使用INSERT方法,我也testing了,似乎并非如此。
运行testing的代码如下。 它还输出.SQL文件,以消除PHP解释器开销
<? //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`"); }
有一个你可以修改的设置叫做'多语句',它禁止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();
希望有所帮助。
你也可能有兴趣在更新上使用连接,这也是可能的。
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.
编辑:如果你正在更新的值不是来自数据库的其他地方,你需要发出多个更新查询。
你可以给同一个表添加你想要插入的id(如果你正在逐行更新:
UPDATE table1 tab1, table1 tab2 -- alias references the same table SET col1 = 1 ,col2 = 2 . . . WHERE tab1.id = tab2.id;
另外,显然你也可以从其他表更新。 在这种情况下,更新双倍作为一个“SELECT”语句,给你从你指定的表中的数据。 您明确地在查询中声明更新值,所以第二个表不受影响。
为什么没有人在一个查询中提到多个陈述 ?
在PHP中,你使用mysqli实例的multi_query
方法。
从PHP手册
MySQL可以允许在一个语句string中有多个语句。 一次发送多个语句减less了客户端 – 服务器往返,但需要特殊的处理。
这里是比较其他3种方法更新30,000原始数据的结果。 代码可以在这里find这是基于@Dakusan的答案
交易:5.5194580554962
插入:0.20669293403625
案例:16.474853992462
多:0.0412278175354
正如你所看到的,多个语句查询比最高的答案更有效率。
如果你得到像这样的错误信息:
PHP Warning: Error while sending SET_OPTION packet
您可能需要增加mysqlconfiguration文件中的max_allowed_packet
,在我的机器上是/etc/mysql/my.cnf
,然后重新启动mysqld。
使用
REPLACE INTO`table` VALUES (`id`,`col1`,`col2`) VALUES (1,6,1),(2,2,3),(3,9,5),(4,16,8);
请注意:
- ID必须是主要的唯一键
- 如果使用外键引用表,则REPLACE将删除然后插入,所以这可能会导致错误
以下将更新一个表中的所有行
Update Table Set Column1 = 'New Value'
下一个将更新列2的值大于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'
可以使用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)中
用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。 我已经testing过了。
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`)
/ /你只是build立在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);
所以你可以用一个查询来更新孔表