回答:
PDOの準備済みステートメントを使用した複数の値の挿入
1つの実行ステートメントに複数の値を挿入する。このページによれば、通常の挿入よりも速いためです。
$datafields = array('fielda', 'fieldb', ... );
$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);
$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);
より多くのデータ値、またはおそらくデータを取り込むループがあります。
準備された挿入では、挿入先のフィールドと、?を作成するフィールドの数を知る必要があります。パラメータをバインドするプレースホルダ。
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();
私のテストでは、複数の挿入と単一の値を持つ通常の準備された挿入を使用した場合、わずか1秒の違いがありました。
placeholders()
度も何度も呼び出す意味はありません。ループの前に一度呼び出して、ループ内にsizeof($datafields)
結果文字列を追加$question_marks[]
します。
Balagtas氏と同じ回答、少し明確...
最近のバージョンのMySQLおよびPHP PDO は、複数行のINSERT
ステートメントをサポートしています。
SQLは、次のようになります。3列のテーブルを作成することを想定しINSERT
ています。
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コードは、通常続く$pdo->prepare($qry)
と$stmt->execute($params)
PDOの呼び出しを。
$params
に渡すすべての値の1次元配列になりますINSERT
。
上記の例では、9つの要素を含める必要があります。PDOは、3のすべてのセットを1行の値として使用します。(各3列3行の挿入= 9要素の配列。)
以下のコードは、効率ではなく、明確にするために書かれています。PHP array_*()
関数を使用して、必要に応じてデータをより適切にマッピングまたはウォークスルーします。トランザクションを使用できるかどうかは、明らかにMySQLのテーブルタイプによって異なります。
仮定:
$tblName
-挿入するテーブルの文字列名$colNames
-テーブルの列名の1次元配列これらの列名は有効なMySQL列識別子である必要があります。ない場合はバッククォート( ``)でエスケープします$dataVals
-多次元配列。各要素は、INSERTする値の行の1次元配列です。// 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();
$rowPlaces
不要:$allPlaces = implode(',', array_fill(0, count($dataVals), '('.str_pad('', (count($colNames)*2)-1, '?,').')'));
votes
UNIQUEを追加unique_index
(user
、email
、address
)。
array_push($dataToInsert, ...array_values($dataVals));
はそれよりはるかに速くなりますforeach ($dataVals as $row => $data) {}
価値があることについては、多くのユーザーが、選択された回答のように単一の文字列クエリとして構築するのではなく、INSERTステートメントを反復することを推奨するのを見てきました。2つのフィールドと非常に基本的な挿入ステートメントだけを使用して、簡単なテストを実行することにしました。
<?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>";
?>
クエリ全体の所要時間は数ミリ秒以下でしたが、後者(単一文字列)のクエリは一貫して8倍以上高速でした。これが、さらに多くの列に数千行のインポートを反映するように構築されている場合、その差は非常に大きくなる可能性があります。
bind_param
2番目のインポートルーチンにステートメントがないのはなぜですか」です。
(?,?)
よね?
$ data配列が小さい場合、Herbert Balagtasによる承認済み回答はうまく機能します。$ data配列が大きくなると、array_merge関数は非常に遅くなります。$ data配列を作成するための私のテストファイルは28列で、約80,000行です。最終的なスクリプトの完了には41秒かかりました。
使用array_pushは()の代わりにarray_mergeの$ insert_valuesを作成するには()の結果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()の必要性を排除するために、代わりに次の2つの配列を構築できます。
//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();
array_push($data, ...array_values($row))
では、の代わりに行うことができます$data = array_merge($data, array_values($row));
。はるかに高速。
array_push()
、php 4でも利用可能です
array_push()
はなく、@ Markが引数のアンパッキングを使用しているためです。...array_values()
そこの呼び出しに気づきましたか?
array_values()
は、php 4でも使用できますargument unpacking
。
2つの可能なアプローチ:
$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();
}
すべての行のデータが単一の配列にある場合、2番目のソリューションを使用します。
$stmt->execute();
はforeachループの外にあるべきだと提案していますか?
それは単に準備されたステートメントを使用する方法ではありません。
異なるパラメーターを使用して1つの準備済みステートメントを複数回実行できるため、クエリごとに1行を挿入しても問題ありません。実際、これは、効率的で安全かつ快適な方法で多数の行を挿入できるため、最大の利点の1つです。
したがって、少なくとも固定された行数に対して、提案したスキームを実装することは可能かもしれませんが、これが実際に望んでいることではないことはほぼ保証されています。
より短い答え:列で並べられたデータの配列をフラットにします
//$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";
1,000個程度のレコードを挿入する場合、必要なのが値の数だけであるときに、すべてのレコードをループして挿入する必要はありません。
これが私の簡単なアプローチです。
$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();
On the readings on PDO, the use prepared statements should give me a better security than static queries.
$workouts_id
が含まれる可能性があり$value
ます。現在ではないかもしれませんが、将来、別の開発者がこのデータを安全でなくなることを保証することはできません。だから私はPDOによって準備されたクエリを作成するほうが正しいと思います。
これは、パージオプションを使用して複数の挿入を行うために記述したクラスです。
<?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';
ここで、2次元配列がすでに準備されているとします。それを反復し、次のように行の値で文字列を作成します。
foreach ( $people as $person ) {
if(! $rowVals ) {
$rows = '(' . "'$name'" . ',' . "'$middleName'" . ',' . "'$eMail'" . ')';
} else { $rowVals = '(' . "'$name'" . ',' . "'$middleName'" . ',' . "'$eMail'" . ')';
}
ここで行ったのは、$ rowsが既に定義されているかどうかを確認し、定義されていない場合は作成し、行の値と必要なSQL構文を保存して、それが有効なステートメントになるようにします。文字列は二重引用符と単一引用符の中に入れる必要があることに注意してください。そうすれば、文字列はすぐにそのように認識されます。
あとは、ステートメントを準備して実行するだけです。
$stmt = $db->prepare ( "INSERT INTO $table $cols VALUES $rowVals" );
$stmt->execute ();
これまでに最大2000行でテストされ、実行時間は悲惨です。さらにテストを実行し、さらに何か貢献することがある場合に備えて、ここに戻ります。
よろしく。
まだ提案されていないため、インデックス付けを無効にし、すべてのデータを挿入してから、インデックスを再度有効にするLOAD DATA INFILEが、データをロードする最も速い方法であると確信しています。
データをcsvとして保存することは、fputcsvを念頭に置いておくとかなり簡単なはずです。MyISAMが最も高速ですが、それでもInnoDBで大きなパフォーマンスが得られます。他の欠点もありますが、100行未満のデータを挿入する必要がなく、大量のデータを挿入する場合は、この方法を使用します。
古い質問はすべての貢献が私を大きく助けてくれたので、自分のDbContext
クラス内で機能する私の解決策を次に示します。$rows
パラメータは、単に行またはモデルを表す連想配列の配列であります:field name => insert value
。
モデルを使用するパターンを使用する場合ToRowArray
、モデルクラス内のメソッドなどから、モデルデータを配列として渡すとうまく適合します。
注:言うまでもありませんが、このメソッドに渡された引数をユーザーに公開したり、検証およびサニタイズされた挿入値以外のユーザー入力に依存したりすることは決して許可しないでください。
$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();
}
$tableName
されているため、ユーザーに公開されていると想定しているだけで、DAL内にあると想定できます。あなたの主張を拡大できますか?物事を言うだけでは役に立たない。
$tableName
でしょうか?
この問題の別の(スリムな)解決策を次に示します。
まず、ソース配列(ここでは$ aData)のデータをcount()でカウントする必要があります。次に、array_fill()を使用して、ソース配列と同じ数のエントリを持つ新しい配列を生成します。各エントリの値は((?、?))です(プレースホルダーの数は、使用するフィールドによって異なります。ここでは2です)。次に、生成された配列を展開し、接着剤としてコンマを使用する必要があります。foreachループ内では、使用するプレースホルダーの数に関する別のインデックスを生成する必要があります(プレースホルダーの数*現在の配列インデックス+ 1)。バインドされた各値の後に、生成されたインデックスに1を追加する必要があります。
$do = $db->prepare("INSERT INTO table (id, name) VALUES ".implode(',', array_fill(0, count($aData), '(?,?)')));
foreach($aData as $iIndex => $aValues){
$iRealIndex = 2 * $iIndex + 1;
$do->bindValue($iRealIndex, $aValues['id'], PDO::PARAM_INT);
$iRealIndex = $iRealIndex + 1;
$do->bindValue($iRealIndex, $aValues['name'], PDO::PARAM_STR);
}
$do->execute();
この関数を使用して、単一のクエリに複数の行を挿入できます。
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')));
これには、単一のクエリで複数の行を挿入しながら、準備済みステートメントを使用できるという利点があります。セキュリティ!
これが私の解決策です: 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);
バグレポートは大歓迎です。
すべてのドイツの郵便番号を空のテーブルに挿入する実際の例(後で町名を追加するため):
// 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);
array_mergeのような重い配列関数なしで機能するクエリ文字列コンストラクターのいくつかを誇りに思っています。特にvsprintf()は良い発見でした。
最後に、メモリ制限を超えないようにするために、2x while()を追加する必要がありました。これはメモリ制限に依存しますが、問題を回避するための優れた一般的な解決策です(10個のクエリを使用しても、10.000よりはるかに優れています)。
test.php
<?php
require_once('Database.php');
$obj = new Database();
$table = "test";
$rows = array(
array(
'name' => 'balasubramani',
'status' => 1
),
array(
'name' => 'balakumar',
'status' => 1
),
array(
'name' => 'mani',
'status' => 1
)
);
var_dump($obj->insertMultiple($table,$rows));
?>
Database.php
<?php
class Database
{
/* Initializing Database Information */
var $host = 'localhost';
var $user = 'root';
var $pass = '';
var $database = "database";
var $dbh;
/* Connecting Datbase */
public function __construct(){
try {
$this->dbh = new PDO('mysql:host='.$this->host.';dbname='.$this->database.'', $this->user, $this->pass);
//print "Connected Successfully";
}
catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
}
/* Insert Multiple Rows in a table */
public function insertMultiple($table,$rows){
$this->dbh->beginTransaction(); // also helps speed up your inserts.
$insert_values = array();
foreach($rows as $d){
$question_marks[] = '(' . $this->placeholders('?', sizeof($d)) . ')';
$insert_values = array_merge($insert_values, array_values($d));
$datafields = array_keys($d);
}
$sql = "INSERT INTO $table (" . implode(",", $datafields ) . ") VALUES " . implode(',', $question_marks);
$stmt = $this->dbh->prepare ($sql);
try {
$stmt->execute($insert_values);
} catch (PDOException $e){
echo $e->getMessage();
}
return $this->dbh->commit();
}
/* placeholders for prepared statements like (?,?,?) */
function placeholders($text, $count=0, $separator=","){
$result = array();
if($count > 0){
for($x=0; $x<$count; $x++){
$result[] = $text;
}
}
return implode($separator, $result);
}
}
?>
私は同じ問題を抱えていましたが、これは私が自分のために達成する方法であり、私は自分のために関数を作成しました(そしてそれがあなたを助けるならそれを使うことができます)。
例:
国(国、都市)VALUES(ドイツ、ベルリン)、(フランス、パリ)に挿入します。
$arr1 = Array("Germany", "Berlin");
$arr2 = Array("France", "France");
insertMultipleData("countries", Array($arr1, $arr2));
// Inserting multiple data to the Database.
public function insertMultipleData($table, $multi_params){
try{
$db = $this->connect();
$beforeParams = "";
$paramsStr = "";
$valuesStr = "";
for ($i=0; $i < count($multi_params); $i++) {
foreach ($multi_params[$i] as $j => $value) {
if ($i == 0) {
$beforeParams .= " " . $j . ",";
}
$paramsStr .= " :" . $j . "_" . $i .",";
}
$paramsStr = substr_replace($paramsStr, "", -1);
$valuesStr .= "(" . $paramsStr . "),";
$paramsStr = "";
}
$beforeParams = substr_replace($beforeParams, "", -1);
$valuesStr = substr_replace($valuesStr, "", -1);
$sql = "INSERT INTO " . $table . " (" . $beforeParams . ") VALUES " . $valuesStr . ";";
$stmt = $db->prepare($sql);
for ($i=0; $i < count($multi_params); $i++) {
foreach ($multi_params[$i] as $j => &$value) {
$stmt->bindParam(":" . $j . "_" . $i, $value);
}
}
$this->close($db);
$stmt->execute();
return true;
}catch(PDOException $e){
return false;
}
return false;
}
// Making connection to the Database
public function connect(){
$host = Constants::DB_HOST;
$dbname = Constants::DB_NAME;
$user = Constants::DB_USER;
$pass = Constants::DB_PASS;
$mysql_connect_str = 'mysql:host='. $host . ';dbname=' .$dbname;
$dbConnection = new PDO($mysql_connect_str, $user, $pass);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $dbConnection;
}
// Closing the connection
public function close($db){
$db = null;
}
場合insertMultipleData($表、$ multi_params)は TRUEを返し、あなたのデータは、データベースに挿入されています。
私の実験に基づいて、単一のトランザクションで複数の値の行を含むmysqlのinsertステートメントが最も高速であることがわかりました。
ただし、データが多すぎる場合、mysqlのmax_allowed_packet
設定により、複数の値行を持つ単一のトランザクション挿入が制限される可能性があります。したがって、mysqlのmax_allowed_packet
サイズより大きいデータがある場合、次の関数は失敗します。
singleTransactionInsertWithRollback
singleTransactionInsertWithPlaceholders
singleTransactionInsert
巨大なデータを挿入するシナリオで最も成功するのはtransactionSpeed
メソッドですが、上記のメソッドよりも時間がかかります。したがって、この問題を処理するには、データを小さなチャンクに分割し、単一のトランザクション挿入を複数回呼び出すか、transactionSpeed
メソッドを使用して実行速度をあきらめることができます。
これが私の研究です
<?php
class SpeedTestClass
{
private $data;
private $pdo;
public function __construct()
{
$this->data = [];
$this->pdo = new \PDO('mysql:dbname=test_data', 'admin', 'admin');
if (!$this->pdo) {
die('Failed to connect to database');
}
}
public function createData()
{
$prefix = 'test';
$postfix = 'unicourt.com';
$salutations = ['Mr.', 'Ms.', 'Dr.', 'Mrs.'];
$csv[] = ['Salutation', 'First Name', 'Last Name', 'Email Address'];
for ($i = 0; $i < 100000; ++$i) {
$csv[] = [
$salutations[$i % \count($salutations)],
$prefix.$i,
$prefix.$i,
$prefix.$i.'@'.$postfix,
];
}
$this->data = $csv;
}
public function truncateTable()
{
$this->pdo->query('TRUNCATE TABLE `name`');
}
public function transactionSpeed()
{
$timer1 = microtime(true);
$this->pdo->beginTransaction();
$sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES (:first_name, :last_name)';
$sth = $this->pdo->prepare($sql);
foreach (\array_slice($this->data, 1) as $values) {
$sth->execute([
':first_name' => $values[1],
':last_name' => $values[2],
]);
}
// $timer2 = microtime(true);
// echo 'Prepare Time: '.($timer2 - $timer1).PHP_EOL;
// $timer3 = microtime(true);
if (!$this->pdo->commit()) {
echo "Commit failed\n";
}
$timer4 = microtime(true);
// echo 'Commit Time: '.($timer4 - $timer3).PHP_EOL;
return $timer4 - $timer1;
}
public function autoCommitSpeed()
{
$timer1 = microtime(true);
$sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES (:first_name, :last_name)';
$sth = $this->pdo->prepare($sql);
foreach (\array_slice($this->data, 1) as $values) {
$sth->execute([
':first_name' => $values[1],
':last_name' => $values[2],
]);
}
$timer2 = microtime(true);
return $timer2 - $timer1;
}
public function noBindAutoCommitSpeed()
{
$timer1 = microtime(true);
foreach (\array_slice($this->data, 1) as $values) {
$sth = $this->pdo->prepare("INSERT INTO `name` (`first_name`, `last_name`) VALUES ('{$values[1]}', '{$values[2]}')");
$sth->execute();
}
$timer2 = microtime(true);
return $timer2 - $timer1;
}
public function singleTransactionInsert()
{
$timer1 = microtime(true);
foreach (\array_slice($this->data, 1) as $values) {
$arr[] = "('{$values[1]}', '{$values[2]}')";
}
$sth = $this->pdo->prepare('INSERT INTO `name` (`first_name`, `last_name`) VALUES '.implode(', ', $arr));
$sth->execute();
$timer2 = microtime(true);
return $timer2 - $timer1;
}
public function singleTransactionInsertWithPlaceholders()
{
$placeholders = [];
$timer1 = microtime(true);
$sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES ';
foreach (\array_slice($this->data, 1) as $values) {
$placeholders[] = '(?, ?)';
$arr[] = $values[1];
$arr[] = $values[2];
}
$sql .= implode(', ', $placeholders);
$sth = $this->pdo->prepare($sql);
$sth->execute($arr);
$timer2 = microtime(true);
return $timer2 - $timer1;
}
public function singleTransactionInsertWithRollback()
{
$placeholders = [];
$timer1 = microtime(true);
$sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES ';
foreach (\array_slice($this->data, 1) as $values) {
$placeholders[] = '(?, ?)';
$arr[] = $values[1];
$arr[] = $values[2];
}
$sql .= implode(', ', $placeholders);
$this->pdo->beginTransaction();
$sth = $this->pdo->prepare($sql);
$sth->execute($arr);
$this->pdo->commit();
$timer2 = microtime(true);
return $timer2 - $timer1;
}
}
$s = new SpeedTestClass();
$s->createData();
$s->truncateTable();
echo "Time Spent for singleTransactionInsertWithRollback: {$s->singleTransactionInsertWithRollback()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for single Transaction Insert: {$s->singleTransactionInsert()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for single Transaction Insert With Placeholders: {$s->singleTransactionInsertWithPlaceholders()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for transaction: {$s->transactionSpeed()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for AutoCommit: {$s->noBindAutoCommitSpeed()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for autocommit with bind: {$s->autoCommitSpeed()}".PHP_EOL;
$s->truncateTable();
2列のみを含むテーブルの100,000エントリの結果は次のとおりです。
$ php data.php
Time Spent for singleTransactionInsertWithRollback: 0.75147604942322
Time Spent for single Transaction Insert: 0.67445182800293
Time Spent for single Transaction Insert With Placeholders: 0.71131205558777
Time Spent for transaction: 8.0056409835815
Time Spent for AutoCommit: 35.4979159832
Time Spent for autocommit with bind: 33.303519010544
これは私のために働いた
$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();
このようなものはどうですか:
if(count($types_of_values)>0){
$uid = 1;
$x = 0;
$sql = "";
$values = array();
foreach($types_of_values as $k=>$v){
$sql .= "(:id_$k,:kind_of_val_$k), ";
$values[":id_$k"] = $uid;
$values[":kind_of_val_$k"] = $v;
}
$sql = substr($sql,0,-2);
$query = "INSERT INTO table (id,value_type) VALUES $sql";
$res = $this->db->prepare($query);
$res->execute($values);
}
この背後にあるアイデアは、配列値を循環させ、準備されたステートメントのプレースホルダーの各ループに「ID番号」を追加すると同時に、バインディングパラメーターの配列に値を追加することです。配列の「キー」インデックスを使用したくない場合は、ループ内に$ i = 0と$ i ++を追加できます。この例ではどちらも機能しますが、名前付きキーを持つ連想配列がある場合でも、キーが一意であれば機能します。少しの作業で、ネストされた配列でも問題ありません。
** substrは$ sql変数の最後のスペースとコンマを取り除きます。スペースがない場合は、これを-2ではなく-1に変更する必要があります。
準備されたクエリを作成するためにここで提供されるソリューションのほとんどは、必要なものよりも複雑です。PHPの組み込み関数を使用すると、大きなオーバーヘッドなしにSQLステートメントを簡単に作成できます。
$records
各レコード自体が(の形式のfield => value
)インデックス付き配列であるレコードの配列が与えられた場合、次の関数は、単一の準備されたステートメントのみを使用$table
して、PDO接続$connection
で指定されたテーブルにレコードを挿入します。への呼び出しで引数アンパッキングを使用しているため、これはPHP 5.6以降のソリューションであることに注意してください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);
}
$stmt->execute($data);
php.net/manual/en/…の多くの回答に注意してください。基本的に、すべてのパラメーターは文字列として検証されて渡されます。クエリを作成した後、データをループし、手動で、bindValue
またはbindParam
第3引数として型を渡します。