PDO Preparedは単一のクエリに複数の行を挿入します


146

現在、MySQLでこのタイプのSQLを使用して、1つのクエリに複数行の値を挿入しています。

INSERT INTO `tbl` (`key1`,`key2`) VALUES ('r1v1','r1v2'),('r2v1','r2v2'),...

PDOの読み取り値では、準備済みステートメントを使用すると、静的クエリよりも優れたセキュリティが得られます。

したがって、準備されたステートメントを使用して「1つのクエリを使用して値の複数の行を挿入する」ことを生成できるかどうかを知りたいのです。

はいの場合、どのように実装できますか?


$stmt->execute($data); php.net/manual/en/…の多くの回答に注意してください。基本的に、すべてのパラメーターは文字列として検証されて渡されます。クエリを作成した後、データをループし、手動で、bindValueまたはbindParam第3引数として型を渡します。
MrMesees 2017

回答:


151

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秒の違いがありました。


4
誤植です。上記の説明では、$ datafieldsについて言及していますが、$ datafieldは$ sqlで使用されています。したがって、コピーと貼り付けはエラーになります。是正してください。この解決策をありがとう。
pal4life 2012

1
これをしばらく使用したところ、単一引用符が含まれる値は適切にエスケープされないことに気付きました。内破に二重引用符を使用することは、私にとっては魅力のように機能します。
qwertzman 2012

1
array_mergeは、array_pushを使用するよりも高価に見えます。
K2xL 2013年

14
「わずか1秒の違いがあった」と言うとき、データを挿入したのは何行ですか。コンテキストによっては1秒はかなり重要です。
Kevin Dice

3
最適化:何placeholders()度も何度も呼び出す意味はありません。ループの前に一度呼び出して、ループ内にsizeof($datafields)結果文字列を追加$question_marks[]します。
AVIDeveloper 2016年

71

Balagtas氏と同じ回答、少し明確...

最近のバージョンのMySQLおよびPHP PDO は、複数行のINSERTステートメントをサポートしています。

SQLの概要

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の概要

あなたの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();

6
PDOがこのように処理するのは本当に残念です。他のDBドライバーでこれを行う非常にエレガントな方法がいくつかあります。
Jonathon 2013年

これは作っていない、もっと簡潔にプレースホルダをセットアップ$rowPlaces不要:$allPlaces = implode(',', array_fill(0, count($dataVals), '('.str_pad('', (count($colNames)*2)-1, '?,').')'));
フィル・

完璧に動作します。この回答に、テーブル内のインデックス(の組み合わせ)の一意性を確保する必要性を追加します。ALTER TABLEのようにvotesUNIQUEを追加unique_indexuseremailaddress)。
ジュゼッペ

1
驚くばかり!ところで、使用array_push($dataToInsert, ...array_values($dataVals));はそれよりはるかに速くなりますforeach ($dataVals as $row => $data) {}
Anis

39

価値があることについては、多くのユーザーが、選択された回答のように単一の文字列クエリとして構築するのではなく、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倍以上高速でした。これが、さらに多くの列に数千行のインポートを反映するように構築されている場合、その差は非常に大きくなる可能性があります。


@ JM4-1 回の実行で10行を直接配置することをお勧めします。しかし、JSONなどのオブジェクトに格納されている数千行を挿入するにはどうすればよいですか?以下の私のコードは正常に動作します。しかし、1回の実行で10行を挿入するように調整するにはどうすればよいですか?`foreach($ json_content as $ datarow){$ id = $ datarow [id]; $ date = $ datarow [date]; $ row3 = $ datarow [row3]; $ row4 = $ datarow [row4]; $ row5 = $ datarow [row5]; $ row6 = $ datarow [row6]; $ row7 = $ datarow [row7]; // $ databaseinsert-> execute();を実行します } // foreachの終了 `
Peter

@ JM4-...そして私の2番目の質問は、「bind_param2番目のインポートルーチンにステートメントがないのはなぜですか」です。
ピーター

あなたは2回ループする必要はありませんか?また、動的にを生成する必要があります(?,?)よね?
NoobishPro 2018

@NoobishProはい、同じfor / foreachを使用して両方を生成できます。
Chazy Chaz

34

$ 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();

4
PHP 5.6 array_push($data, ...array_values($row))では、の代わりに行うことができます$data = array_merge($data, array_values($row));。はるかに高速。
mpen 2015

なぜ5.6なのですか?ドキュメントには5.6については何も書かれておらずarray_push()、php 4でも利用可能です
ZurabWeb

1
@PieroはPHP 5.6+のみのコードです。これは、の使用によるものでarray_push()はなく、@ Markが引数のアンパッキングを使用しているためです。...array_values()そこの呼び出しに気づきましたか?
mariano.iglesias

@ mariano.iglesias array_values()は、php 4でも使用できますargument unpacking
ZurabWeb 2015

2
@Piero、引数の解凍は、PHP 5.6で導入された機能です。これは、複数の引数を配列として提供する方法です。ここをチェック- php.net/manual/en/...
アニス

14

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番目のソリューションを使用します。


10
後者では、1つのステートメントに結合するのではなく、いくつか(場合によっては数千)の個別の実行呼び出しを行いませんか?
JM4 2012年

@ JM4、あなた$stmt->execute();はforeachループの外にあるべきだと提案していますか?
bafromca 2013

@bafromca-はい、そうです。上記の私の回答と賛成投票をご覧ください。純粋な挿入ステートメントでは、単一のステートメントではないという論理的に思いつくことができる理由はありません。1つの呼び出し、1つの実行。実際、2012年の初めからの私の答えはさらに改善される可能性があります-時間があれば、後で行うことになります。挿入/更新/削除の組み合わせを投入し始めると、それは別の話になります。
JM4 2013

12

それは単に準備されたステートメントを使用する方法ではありません。

異なるパラメーターを使用して1つの準備済みステートメントを複数回実行できるため、クエリごとに1行を挿入しても問題ありません。実際、これは、効率的で安全かつ快適な方法で多数の行を挿入できるため、最大の利点の1つです。

したがって、少なくとも固定された行数に対して、提案したスキームを実装することは可能かもしれませんが、これが実際に望んでいることではないことはほぼ保証されています。


1
テーブルに複数の行を挿入するより良い方法を提案できますか?
クラッシュサッチ2014

@Crashthatch:単純な方法で実行してください:準備済みステートメントを1回セットアップしてから、バインドされたパラメーターの値が異なる各行に対してそれを実行します。これがZykの回答の2番目のアプローチです。
sebasgo 14

2
準備された声明で述べた目的は正しいです。ただし、マルチ挿入の使用は挿入速度を向上させるためのもう1つの手法であり、準備済みステートメントでも使用できます。私の経験では、PDOの準備済みステートメントを使用して3,000万行のデータを移行しているときに、マルチ挿入の方がトランザクションでグループ化された単一挿入よりも7〜10倍高速であることがわかりました。
Anis 2017

1
絶対にアニスに同意します。10万行あり、muri行の挿入により速度が大幅に向上します。
ケネス

行ごとに1回ループでリレーショナルデータベースを呼び出すことは一般に良いことだと主張することは、私には同意できません。そのための反対投票。もちろん、大丈夫です。私は工学の絶対主義を信じていません。しかし、これは特定の場合にのみ使用するべきアンチパターンです。
Brandon、

8

より短い答え:列で並べられたデータの配列をフラットにします

//$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個程度のレコードを挿入する場合、必要なのが値の数だけであるときに、すべてのレコードをループして挿入する必要はありません。


5

これが私の簡単なアプローチです。

    $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();

6
準備されたステートメントを使用するという点を打ち負かしています。opは問題のセキュリティを懸念していますOn the readings on PDO, the use prepared statements should give me a better security than static queries.
YesItsMe

2
検証されていないことをイメージングするだけで、かなり予期しないデータ$workouts_idが含まれる可能性があり$valueます。現在ではないかもしれませんが、将来、別の開発者がこのデータを安全でなくなることを保証することはできません。だから私はPDOによって準備されたクエリを作成するほうが正しいと思います。
Nikita_kharkov_ua

3

これは、パージオプションを使用して複数の挿入を行うために記述したクラスです。

<?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;
    }
}

こんにちはピエール。たぶん、あなたはもうこの辺で活動していません。それにもかかわらず、私はこの問題に対する私の考えがあなたのものとほとんど同じに見えることを指摘したかっただけです。純粋に偶然ですが、これ以上のことはないと思います。DELETE- AND UPDATE-Operationsのクラスも追加し、その後、ここからいくつかのアイデアを取り入れました。私はあなたのクラスを見なかった。私の恥知らずな自己宣伝はここで失礼しますが、誰かのために役立つと思います。これがSOルールに反しないことを願っています。こちらで見つけてください
JackLeEmmerdeur 2017

1

これは私がそれをした方法です:

最初に、使用する列名を定義するか、空白のままにしておくと、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行でテストされ、実行時間は悲惨です。さらにテストを実行し、さらに何か貢献することがある場合に備えて、ここに戻ります。

よろしく。


1

まだ提案されていないため、インデックス付けを無効にし、すべてのデータを挿入してから、インデックスを再度有効にするLOAD DATA INFILEが、データをロードする最も速い方法であると確信しています。

データをcsvとして保存することは、fputcsvを念頭に置いておくとかなり簡単なはずです。MyISAMが最も高速ですが、それでもInnoDBで大きなパフォーマンスが得られます。他の欠点もありますが、100行未満のデータを挿入する必要がなく、大量のデータを挿入する場合は、この方法を使用します。


1

古い質問はすべての貢献が私を大きく助けてくれたので、自分の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();
}

単一のクエリにトランザクションを使用しても意味がないため、トランザクションを削除します。そしていつものように、このコードはSQLインジェクションまたはクエリエラーに対して脆弱です。
常識

この場合のトランザクションの冗長な使用についてはあなたは正しいですが、これがSQLインジェクションに対してどのように脆弱であるかはわかりません。これはパラメーター化$tableNameされているため、ユーザーに公開されていると想定しているだけで、DAL内にあると想定できます。あなたの主張を拡大できますか?物事を言うだけでは役に立たない。
リー

まあ、それはテーブル名だけでなく、とにかくここに投稿したコードを使用
常識

したがって、コードのすべての潜在的な使用または引数のすべてのソースを概説するのは投稿者の責任ですか?たぶん私は人々の期待が高まっています。ユーザーがアクセスできないようにするためのメモを追加した方がよい$tableNameでしょうか?
リー

見せびらかすだけでなく誰かを助けることを意図している場合、信頼できるコードを投稿するのは投稿者の責任です。
常識

1

この問題の別の(スリムな)解決策を次に示します。

まず、ソース配列(ここでは$ 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();

0

この関数を使用して、単一のクエリに複数の行を挿入できます。

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')));

これには、単一のクエリで複数の行を挿入しながら、準備済みステートメントを使用できるという利点があります。セキュリティ!


0

これが私の解決策です: 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);

バグレポートは大歓迎です。


2.4以降では、github.com / auraphp / Aura.SqlQuery / tree / を使用してマルチ挿入を作成し、ExtendedPdoを使用して:)を実行できます。
Hari KT

0

すべてのドイツの郵便番号を空のテーブルに挿入する実際の例(後で町名を追加するため):

// 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よりはるかに優れています)。


0

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);
    }

}
?>

Stackoverflowへようこそ。コードだけでなく、問題を投稿して説明してください。
Prakash Palnati 2017年

基本的に。それは、受け入れられた回答で提供されたコードの実装にすぎません
常識

0

私は同じ問題を抱えていましたが、これは私が自分のために達成する方法であり、私は自分のために関数を作成しました(そしてそれがあなたを助けるならそれを使うことができます)。

例:

国(国、都市)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を返し、あなたのデータは、データベースに挿入されています。


0

私の実験に基づいて、単一のトランザクションで複数の値の行を含むmysqlのinsertステートメントが最も高速であることがわかりました。

ただし、データが多すぎる場合、mysqlのmax_allowed_packet設定により、複数の値行を持つ単一のトランザクション挿入が制限される可能性があります。したがって、mysqlのmax_allowed_packetサイズより大きいデータがある場合、次の関数は失敗します。

  1. singleTransactionInsertWithRollback
  2. singleTransactionInsertWithPlaceholders
  3. 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

0

これは私のために働いた

$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();

0

このようなものはどうですか:

        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に変更する必要があります。


-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);
}

1
このコードはSQLインジェクションに対して脆弱であるため使用しないでください
常識
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.