准备PDO在单个查询中插入多行
我目前在MySQL上使用这种types的SQL来在单个查询中插入多行值:
INSERT INTO `tbl` (`key1`,`key2`) VALUES ('r1v1','r1v2'),('r2v1','r2v2'),...
在PDO的阅读上,使用准备好的语句应该比静态查询提供更好的安全性。
因此,我想知道是否可以使用预处理语句生成“通过使用一个查询插入多行值”。
如果是的话,我可否知道我该如何执行?
使用PDO准备好的语句插入多个值
在一个执行语句中插入多个值。 为什么因为根据这个页面,它比普通的插入更快。 http://dev.mysql.com/doc/refman/5.0/en/insert-speed.html
$datafields = array('fielda', 'fieldb', ... ); $data[] = array('fielda' => 'value', 'fieldb' => 'value' ....); $data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);
更多的数据值,或者你可能有一个循环来填充数据。
准备插入,你需要知道你插入的领域,以及创build的字段数? 占位符绑定您的参数。
insert into table (fielda, fieldb, ... ) values (?,?...), (?,?...)....
这基本上是我们希望插入语句看起来像。
现在,代码:
function placeholders($text, $count=0, $separator=","){ $result = array(); if($count > 0){ for($x=0; $x<$count; $x++){ $result[] = $text; } } return implode($separator, $result); } $pdo->beginTransaction(); // also helps speed up your inserts. $insert_values = array(); foreach($data as $d){ $question_marks[] = '(' . placeholders('?', sizeof($d)) . ')'; $insert_values = array_merge($insert_values, array_values($d)); } $sql = "INSERT INTO table (" . implode(",", $datafields ) . ") VALUES " . implode(',', $question_marks); $stmt = $pdo->prepare ($sql); try { $stmt->execute($insert_values); } catch (PDOException $e){ echo $e->getMessage(); } $pdo->commit();
虽然在我的testing中,使用多个插入和单一值的常规准备插入只有1秒的差异。
与Balagtas先生一样的答案,稍微清楚些
最近的版本MySQL和PHP PDO支持多行INSERT
语句。
SQL概述
SQL将看起来像这样,假设你想INSERT
到3列表。
INSERT INTO tbl_name (colA, colB, colC) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) [,...]
ON DUPLICATE KEY UPDATE
按预期工作,即使使用多行INSERT; 追加这个:
ON DUPLICATE KEY UPDATE colA = VALUES(colA), colB = VALUES(colB), colC = VALUES(colC)
PHP概述
您的PHP代码将遵循通常的$pdo->prepare($qry)
和$stmt->execute($params)
PDO调用。
$params
将是传递给INSERT
的所有值的一维数组。
在上面的例子中,它应该包含9个元素; PDO将使用每一组3作为单行值。 (插入3行,每列3列= 9个元素数组。
履行
下面的代码是为了清晰而不是效率而编写的。 使用PHP array_*()
函数可以更好地映射或遍历数据,如果您愿意的话。 你是否可以使用事务显然取决于你的MySQL表types。
假设:
-
$tblName
– 要插入到的表的string名称 -
$colNames
– 表的列名称的一维数组这些列名必须是有效的MySQL列标识符; 如果不是的话,用反引号(“)来逃避它们 -
$dataVals
– multidimensional array,其中每个元素是INSERT的一行值的一维数组
示例代码
// setup data values for PDO // memory warning: this is creating a copy all of $dataVals $dataToInsert = array(); foreach ($dataVals as $row => $data) { foreach($data as $val) { $dataToInsert[] = $val; } } // (optional) setup the ON DUPLICATE column names $updateCols = array(); foreach ($colNames as $curCol) { $updateCols[] = $curCol . " = VALUES($curCol)"; } $onDup = implode(', ', $updateCols); // setup the placeholders - a fancy way to make the long "(?, ?, ?)..." string $rowPlaces = '(' . implode(', ', array_fill(0, count($colNames), '?')) . ')'; $allPlaces = implode(', ', array_fill(0, count($dataVals), $rowPlaces)); $sql = "INSERT INTO $tblName (" . implode(', ', $colNames) . ") VALUES " . $allPlaces . " ON DUPLICATE KEY UPDATE $onDup"; // and then the PHP PDO boilerplate $stmt = $pdo->prepare ($sql); try { $stmt->execute($dataToInsert); } catch (PDOException $e){ echo $e->getMessage(); } $pdo->commit();
对于什么是值得的,我看到很多用户build议迭代通过INSERT语句,而不是作为一个单一的string查询作为选定的答案。 我决定用两个字段和一个非常基本的插入语句来运行一个简单的testing:
<?php require('conn.php'); $fname = 'J'; $lname = 'M'; $time_start = microtime(true); $stmt = $db->prepare('INSERT INTO table (FirstName, LastName) VALUES (:fname, :lname)'); for($i = 1; $i <= 10; $i++ ) { $stmt->bindParam(':fname', $fname); $stmt->bindParam(':lname', $lname); $stmt->execute(); $fname .= 'O'; $lname .= 'A'; } $time_end = microtime(true); $time = $time_end - $time_start; echo "Completed in ". $time ." seconds <hr>"; $fname2 = 'J'; $lname2 = 'M'; $time_start2 = microtime(true); $qry = 'INSERT INTO table (FirstName, LastName) VALUES '; $qry .= "(?,?), "; $qry .= "(?,?), "; $qry .= "(?,?), "; $qry .= "(?,?), "; $qry .= "(?,?), "; $qry .= "(?,?), "; $qry .= "(?,?), "; $qry .= "(?,?), "; $qry .= "(?,?), "; $qry .= "(?,?)"; $stmt2 = $db->prepare($qry); $values = array(); for($j = 1; $j<=10; $j++) { $values2 = array($fname2, $lname2); $values = array_merge($values,$values2); $fname2 .= 'O'; $lname2 .= 'A'; } $stmt2->execute($values); $time_end2 = microtime(true); $time2 = $time_end2 - $time_start2; echo "Completed in ". $time2 ." seconds <hr>"; ?>
虽然整个查询本身需要几毫秒或更less的时间,但后者(单个string)查询一直快8倍或更多。 如果这个build议反映出在更多的专栏上有数千行的input,那么差别可能是巨大的。
当$ data数组很小时,Herbert Balagtas所接受的答案很好。 对于较大的$ data数组,array_merge函数变得非常慢。 我创build$ data数组的testing文件有28列,大约有80,000行。 最后的脚本花了41s完成。
使用array_push()创build$ insert_values而不是array_merge()导致100X加速 ,执行时间为0.41s 。
有问题的array_merge():
$insert_values = array(); foreach($data as $d){ $question_marks[] = '(' . placeholders('?', sizeof($d)) . ')'; $insert_values = array_merge($insert_values, array_values($d)); }
为了消除对array_merge()的需求,你可以build立下面两个数组:
//Note that these fields are empty, but the field count should match the fields in $datafields. $data[] = array('','','','',... n ); //getting rid of array_merge() array_push($insert_values, $value1, $value2, $value3 ... n );
这些数组可以按如下方式使用:
function placeholders($text, $count=0, $separator=","){ $result = array(); if($count > 0){ for($x=0; $x<$count; $x++){ $result[] = $text; } } return implode($separator, $result); } $pdo->beginTransaction(); foreach($data as $d){ $question_marks[] = '(' . placeholders('?', sizeof($d)) . ')'; } $sql = "INSERT INTO table (" . implode(",", array_keys($datafield) ) . ") VALUES " . implode(',', $question_marks); $stmt = $pdo->prepare ($sql); try { $stmt->execute($insert_values); } catch (PDOException $e){ echo $e->getMessage(); } $pdo->commit();
这根本不是你使用准备好的语句的方式。
为每个查询插入一行是完全可以的,因为您可以使用不同的参数多次执行一个准备好的语句。 事实上,这是最大的优势之一,因为它可以让您以高效,安全和舒适的方式插入大量的行。
所以有可能实现你提出的scheme,至less对于固定数量的行,但几乎可以保证这不是你想要的。
两种可能的方法:
$stmt = $pdo->prepare('INSERT INTO foo VALUES(:v1_1, :v1_2, :v1_3), (:v2_1, :v2_2, :v2_3), (:v2_1, :v2_2, :v2_3)'); $stmt->bindValue(':v1_1', $data[0][0]); $stmt->bindValue(':v1_2', $data[0][1]); $stmt->bindValue(':v1_3', $data[0][2]); // etc... $stmt->execute();
要么:
$stmt = $pdo->prepare('INSERT INTO foo VALUES(:a, :b, :c)'); foreach($data as $item) { $stmt->bindValue(':a', $item[0]); $stmt->bindValue(':b', $item[1]); $stmt->bindValue(':c', $item[2]); $stmt->execute(); }
如果所有行的数据都在一个数组中,我会使用第二个解决scheme。
$stmt = $db->prepare('INSERT INTO ids VALUES (0, :url)'); try { $db->beginTransaction(); foreach ($ursl as $url) { $stmt->bindValue(':url', $url); $stmt->execute(); } $db->commit(); } catch (PDOException $e) { $db->rollBack(); }
一个较短的答案:扁平按列sorting的数据然后
//$array = array( '1','2','3','4','5', '1','2','3','4','5'); $arCount = count($array); $rCount = ($arCount ? $arCount - 1 : 0); $criteria = sprintf("(?,?,?,?,?)%s", str_repeat(",(?,?,?,?,?)", $rCount)); $sql = "INSERT INTO table(c1,c2,c3,c4,c5) VALUES$criteria";
当插入一个1000条左右的logging时,你不需要遍历每条logging来插入它们,当你需要的只是一个数值。
这里是我写的一个类做多个插入与清除选项:
<?php /** * $pdo->beginTransaction(); * $pmi = new PDOMultiLineInserter($pdo, "foo", array("a","b","c","e"), 10); * $pmi->insertRow($data); * .... * $pmi->insertRow($data); * $pmi->purgeRemainingInserts(); * $pdo->commit(); * */ class PDOMultiLineInserter { private $_purgeAtCount; private $_bigInsertQuery, $_singleInsertQuery; private $_currentlyInsertingRows = array(); private $_currentlyInsertingCount = 0; private $_numberOfFields; private $_error; private $_insertCount = 0; function __construct(\PDO $pdo, $tableName, $fieldsAsArray, $bigInsertCount = 100) { $this->_numberOfFields = count($fieldsAsArray); $insertIntoPortion = "INSERT INTO `$tableName` (`".implode("`,`", $fieldsAsArray)."`) VALUES"; $questionMarks = " (?".str_repeat(",?", $this->_numberOfFields - 1).")"; $this->_purgeAtCount = $bigInsertCount; $this->_bigInsertQuery = $pdo->prepare($insertIntoPortion.$questionMarks.str_repeat(", ".$questionMarks, $bigInsertCount - 1)); $this->_singleInsertQuery = $pdo->prepare($insertIntoPortion.$questionMarks); } function insertRow($rowData) { // @todo Compare speed // $this->_currentlyInsertingRows = array_merge($this->_currentlyInsertingRows, $rowData); foreach($rowData as $v) array_push($this->_currentlyInsertingRows, $v); // if (++$this->_currentlyInsertingCount == $this->_purgeAtCount) { if ($this->_bigInsertQuery->execute($this->_currentlyInsertingRows) === FALSE) { $this->_error = "Failed to perform a multi-insert (after {$this->_insertCount} inserts), the following errors occurred:".implode('<br/>', $this->_bigInsertQuery->errorInfo()); return false; } $this->_insertCount++; $this->_currentlyInsertingCount = 0; $this->_currentlyInsertingRows = array(); } return true; } function purgeRemainingInserts() { while ($this->_currentlyInsertingCount > 0) { $singleInsertData = array(); // @todo Compare speed - http://www.evardsson.com/blog/2010/02/05/comparing-php-array_shift-to-array_pop/ // for ($i = 0; $i < $this->_numberOfFields; $i++) $singleInsertData[] = array_pop($this->_currentlyInsertingRows); array_reverse($singleInsertData); for ($i = 0; $i < $this->_numberOfFields; $i++) array_unshift($singleInsertData, array_pop($this->_currentlyInsertingRows)); if ($this->_singleInsertQuery->execute($singleInsertData) === FALSE) { $this->_error = "Failed to perform a small-insert (whilst purging the remaining rows; the following errors occurred:".implode('<br/>', $this->_singleInsertQuery->errorInfo()); return false; } $this->_currentlyInsertingCount--; } } public function getError() { return $this->_error; } }
这是我做到的:
首先定义要使用的列名,或者将其保留为空,并且pdo将假定您要使用表中的所有列 – 在这种情况下,您需要按照它们出现在表上的顺序通知行值。
$cols = 'name', 'middleName', 'eMail'; $table = 'people';
现在,假设你有一个二维数组已经准备好了。 迭代它,然后用你的行值构造一个string,如下所示:
foreach ( $people as $person ) { if(! $rowVals ) { $rows = '(' . "'$name'" . ',' . "'$middleName'" . ',' . "'$eMail'" . ')'; } else { $rowVals = '(' . "'$name'" . ',' . "'$middleName'" . ',' . "'$eMail'" . ')'; }
现在,你刚刚做的是检查$行是否已经定义,如果没有,创build它并存储行值和必要的SQL语法,所以它将是一个有效的语句。 请注意,string应该放在双引号和单引号内,这样它们将被及时识别。
所有剩下要做的就是准备好声明并执行,如下所示:
$stmt = $db->prepare ( "INSERT INTO $table $cols VALUES $rowVals" ); $stmt->execute ();
迄今为止testing了多达2000行,执行时间是令人沮丧的。 将运行一些更多的testing,并会回到这里,以便我有进一步的贡献。
问候。
这是我简单的方法。
$values = array(); foreach($workouts_id as $value){ $_value = "(".$value.",".$plan_id.")"; array_push($values,$_value); } $values_ = implode(",",$values); $sql = "INSERT INTO plan_days(id,name) VALUES" . $values_.""; $stmt = $this->conn->prepare($sql); $stmt->execute();
您可以使用此function在单个查询中插入多行:
function insertMultiple($query,$rows) { if (count($rows)>0) { $args = array_fill(0, count($rows[0]), '?'); $params = array(); foreach($rows as $row) { $values[] = "(".implode(',', $args).")"; foreach($row as $value) { $params[] = $value; } } $query = $query." VALUES ".implode(',', $values); $stmt = $PDO->prepare($query); $stmt->execute($params); } }
$ row是一个数组数组。 在你的情况下,你会调用该函数
insertMultiple("INSERT INTO tbl (`key1`,`key2`)",array(array('r1v1','r1v2'),array('r2v1','r2v2')));
这有利于您使用预准备语句 ,同时用单个查询插入多行。 安全!
这对我有效
$sql = 'INSERT INTO table(pk_pk1,pk_pk2,date,pk_3) VALUES '; $qPart = array_fill(0, count($array), "(?, ?,UTC_TIMESTAMP(),?)"); $sql .= implode(",", $qPart); $stmt = DB::prepare('base', $sql); $i = 1; foreach ($array as $value) { $stmt->bindValue($i++, $value); $stmt->bindValue($i++, $pk_pk1); $stmt->bindValue($i++, $pk_pk2); $stmt->bindValue($i++, $pk_pk3); } $stmt->execute();
这里是我的解决scheme:基于auraphp / Aura.Sql库的https://github.com/sasha-ch/Aura.Sql 。
用法示例:
$q = "insert into t2(id,name) values (?,?), ... on duplicate key update name=name"; $bind_values = [ [[1,'str1'],[2,'str2']] ]; $pdo->perform($q, $bind_values);
Bugreports是受欢迎的。
我真实世界的例子,将所有德国邮政编码插入一个空表(以后添加城镇名称):
// obtain column template $stmt = $db->prepare('SHOW COLUMNS FROM towns'); $stmt->execute(); $columns = array_fill_keys(array_values($stmt->fetchAll(PDO::FETCH_COLUMN)), null); // multiple INSERT $postcode = '01000';// smallest german postcode while ($postcode <= 99999) {// highest german postcode $values = array(); while ($postcode <= 99999) { // reset row $row = $columns; // now fill our row with data $row['postcode'] = sprintf('%05d', $postcode); // build INSERT array foreach ($row as $value) { $values[] = $value; } $postcode++; // avoid memory kill if (!($postcode % 10000)) { break; } } // build query $count_columns = count($columns); $placeholder = ',(' . substr(str_repeat(',?', $count_columns), 1) . ')';//,(?,?,?) $placeholder_group = substr(str_repeat($placeholder, count($values) / $count_columns), 1);//(?,?,?),(?,?,?)... $into_columns = implode(',', array_keys($columns));//col1,col2,col3 // this part is optional: $on_duplicate = array(); foreach ($columns as $column => $row) { $on_duplicate[] = $column; $on_duplicate[] = $column; } $on_duplicate = ' ON DUPLICATE KEY UPDATE' . vsprintf(substr(str_repeat(', %s = VALUES(%s)', $count_columns), 1), $on_duplicate); // execute query $stmt = $db->prepare('INSERT INTO towns (' . $into_columns . ') VALUES' . $placeholder_group . $on_duplicate);//INSERT INTO towns (col1,col2,col3) VALUES(?,?,?),(?,?,?)... {ON DUPLICATE...} $stmt->execute($values); }
你可以看到它完全灵活。 您不需要检查列的数量或检查列的位置。 您只需要设置插入数据:
$row['postcode'] = sprintf('%05d', $postcode);
我为一些查询string构造函数感到自豪,因为它们不需要像array_merge这样的重数组函数。 特别是vsprintf()是一个很好的发现。
最后,我需要添加两个while()以避免超出内存限制。 这取决于你的内存限制,但在所有的一个很好的通用的解决scheme,以避免问题(并有10个查询仍然远远优于10.000)。
由于尚未提出build议,因此我非常肯定LOAD DATA INFILE仍然是加载数据的最快方式,因为它会禁用索引,插入所有数据,然后重新启用索引 – 全部在一个请求中。
牢记fputcsv,将数据保存为csv应该相当简单。 MyISAM是最快的,但是你仍然在InnoDB中获得巨大的性能。 还有其他的一些缺点,尽pipe如此,如果你插入大量的数据,我会走这条路线,而不会打扰100行以下。
这里给出的大多数用于创build准备好的查询的解决scheme都是比较复杂的。 使用PHP的内置函数,您可以轻松地创buildSQL语句,而无需大量开销。
给定$records
是一个$records
数组,其中每个logging本身就是一个索引数组( field => value
的forms),下面的函数将把logging插入到PDO连接$connection
的给定表$table
一个准备好的声明。 请注意,这是一个PHP 5.6+解决scheme,因为在调用array_push
使用了参数解包:
private function import(PDO $connection, $table, array $records) { $fields = array_keys($records[0]); $placeHolders = substr(str_repeat(',?', count($fields)), 1); $values = []; foreach ($records as $record) { array_push($values, ...array_values($record)); } $query = 'INSERT INTO ' . $table . ' ('; $query .= implode(',', $fields); $query .= ') VALUES ('; $query .= implode('),(', array_fill(0, count($records), $placeHolders)); $query .= ')'; $statement = $connection->prepare($query); $statement->execute($values); }
数组联合应该比array_push
更快,所以如下所示:
$cumulativeArray += $rowArray;
虽然老问题的所有贡献帮助了我很多,所以这里是我的解决scheme,它在我自己的DbContext
类中工作。 $rows
参数只是一个表示行或模型的关联数组的数组: field name => insert value
。
如果使用使用模型的模式,则在将模型数据作为数组传递时,这很适合,例如从模型类中的ToRowArray
方法。
注意 :它应该不言而喻,但决不允许传递给这个方法的参数暴露给用户或者依赖于除插入值之外的任何用户input,这些值已被validation和消毒。
$tableName
参数和列名应该由调用逻辑定义; 例如,User
模型可以被映射到用户表,该表的列列表被映射到模型的成员字段。
public function InsertRange($tableName, $rows) { // Get column list $columnList = array_keys($rows[0]); $numColumns = count($columnList); $columnListString = implode(",", $columnList); // Generate pdo param placeholders $placeHolders = array(); foreach($rows as $row) { $temp = array(); for($i = 0; $i < count($row); $i++) $temp[] = "?"; $placeHolders[] = "(" . implode(",", $temp) . ")"; } $placeHolders = implode(",", $placeHolders); // Construct the query $sql = "insert into $tableName ($columnListString) values $placeHolders"; $stmt = $this->pdo->prepare($sql); $j = 1; foreach($rows as $row) { for($i = 0; $i < $numColumns; $i++) { $stmt->bindParam($j, $row[$columnList[$i]]); $j++; } } $stmt->execute(); }