it-swarm.com.de

PDO Prepared Fügt mehrere Zeilen in eine Abfrage ein

Derzeit verwende ich diese Art von SQL unter MySQL, um mehrere Wertzeilen in eine einzige Abfrage einzufügen:

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

Bei den Messwerten im PDO sollten die gebrauchsfertigen Anweisungen eine bessere Sicherheit bieten als statische Abfragen.

Ich möchte daher wissen, ob es möglich ist, mit vorbereiteten Anweisungen "Einfügen mehrerer Wertzeilen durch Verwendung einer Abfrage" zu generieren. 

Wenn ja, kann ich wissen, wie ich es implementieren kann?

124
hoball

Mehrere Werte einfügen mit PDO-Anweisungen

Einfügen mehrerer Werte in eine Ausführungsanweisung Warum denn laut dieser Seite ist es schneller als normale Einfügungen. 

$datafields = array('fielda', 'fieldb', ... );

$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);
$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);

mehr Datenwerte oder Sie haben wahrscheinlich eine Schleife, die Daten auffüllt.

Bei vorbereiteten Beilagen müssen Sie die Felder kennen, in die Sie einfügen, und die Anzahl der Felder, in denen das Feld erstellt werden soll. Platzhalter, um Ihre Parameter zu binden.

insert into table (fielda, fieldb, ... ) values (?,?...), (?,?...)....

Das ist im Grunde, wie die insert-Anweisung aussehen soll.

Nun der Code:

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

In meinem Test gab es zwar nur einen Unterschied von 1 Sekunde, wenn mehrere Inserts und regelmäßig vorbereitete Inserts mit Einzelwert verwendet wurden.

129

Gleiche Antwort wie Herr Balagtas, etwas klarer ...

Neueste Versionen MySQL und PHP PDO do unterstützen mehrzeilige INSERT-Anweisungen.

SQL-Übersicht

SQL sieht ungefähr so ​​aus, vorausgesetzt, eine 3-spaltige Tabelle, zu der Sie INSERT möchten.

INSERT INTO tbl_name
            (colA, colB, colC)
     VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) [,...]

ON DUPLICATE KEY UPDATE funktioniert auch bei mehrzeiligem INSERT wie erwartet; fügen Sie das an:

ON DUPLICATE KEY UPDATE colA = VALUES(colA), colB = VALUES(colB), colC = VALUES(colC)

PHP-Übersicht

Ihr PHP -Code folgt den üblichen PDO-Aufrufen $pdo->prepare($qry) und $stmt->execute($params).

$params wird ein eindimensionales Array von all sein, die an INSERT übergeben werden.

Im obigen Beispiel sollte es 9 Elemente enthalten. PDO verwendet jeden Satz von 3 als eine einzige Zeile mit Werten. (Einfügen von 3 Zeilen mit jeweils 3 Spalten = Array mit 9 Elementen.)

Implementierung

Der folgende Code wurde aus Gründen der Übersichtlichkeit und nicht der Effizienz geschrieben. Arbeiten Sie mit den PHP array_*()-Funktionen, um bessere Möglichkeiten zur Zuordnung Ihrer Daten zu erhalten, wenn Sie möchten. Ob Sie Transaktionen verwenden können, hängt natürlich von Ihrem MySQL-Tabellentyp ab.

Vorausgesetzt:

  • $tblName - der Name der Tabelle, in die INSERT eingefügt werden soll
  • $colNames - 1-dimensionales Array der Spaltennamen der Tabelle Diese Spaltennamen müssen gültige MySQL-Spaltenbezeichner sein. entgehen Sie ihnen mit Backticks (``), falls dies nicht der Fall ist
  • $dataVals - mehrdimensionales Array, wobei jedes Element ein 1-d-Array aus einer Zeile von Werten für INSERT ist

Beispielcode

// 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();
65
jamesvl

Für das, was es wert ist, habe ich schon viele Benutzer empfohlen, die das Durchlaufen von INSERT-Anweisungen empfohlen haben, anstatt sich wie eine ausgewählte String-Abfrage zu bilden. Ich entschied mich für einen einfachen Test mit nur zwei Feldern und einer sehr einfachen Einfügeanweisung:

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

Während die gesamte Abfrage selbst Millisekunden oder weniger dauerte, war die letzte Abfrage (einzelne Zeichenfolge) durchgängig mindestens achtmal schneller. Wenn dies so aufgebaut ist, dass Tausende von Zeilen in vielen weiteren Spalten importiert werden, könnte der Unterschied enorm sein.

37
JM4

Die akzeptierte Antwort von Herbert Balagtas funktioniert gut, wenn das $ data-Array klein ist. Bei größeren $ data-Arrays wird die array_merge-Funktion unerschwinglich langsam. Meine Testdatei zum Erstellen des $ data-Arrays hat 28 Spalten und umfasst etwa 80.000 Zeilen. Das endgültige Skript benötigte 41s , um abzuschließen.

Die Verwendung von array_Push () zum Erstellen von $ insert_values ​​anstelle von array_merge () führte zu einer 100X-Beschleunigung mit einer Ausführungszeit von 0,41s .

Das problematische array_merge ():

$insert_values = array();

foreach($data as $d){
 $question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
 $insert_values = array_merge($insert_values, array_values($d));
}

Um die Notwendigkeit für array_merge () zu vermeiden, können Sie stattdessen die folgenden zwei Arrays erstellen:

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

Diese Arrays können dann wie folgt verwendet werden:

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();
29
Chris M.

Zwei mögliche Ansätze:

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

Oder:

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

Wenn die Daten für alle Zeilen in einem einzigen Array liegen, würde ich die zweite Lösung verwenden.

13
Zyx

Das ist einfach nicht die Art, wie Sie vorbereitete Anweisungen verwenden. 

Es ist vollkommen in Ordnung, eine Zeile pro Abfrage einzufügen, da Sie eine vorbereitete Anweisung mehrmals mit verschiedenen Parametern ausführen können. In der Tat ist dies einer der größten Vorteile, da Sie eine große Anzahl von Reihen effizient, sicher und komfortabel einfügen können.

So ist es vielleicht möglich, das von Ihnen vorgeschlagene Schema zumindest für eine feste Anzahl von Zeilen zu implementieren, aber es ist fast garantiert, dass dies nicht wirklich das ist, was Sie wollen.

12
sebasgo

Eine kürzere Antwort: Reduzieren Sie das Array der Daten nach Spalten

//$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";

Wenn Sie etwa 1.000 Datensätze einfügen, müssen Sie nicht jeden Datensatz durchlaufen, um sie einzufügen, wenn Sie nur eine Anzahl der Werte benötigen.

7
fyrye

Hier ist eine Klasse, die ich geschrieben habe, um mehrere Einfügungen mit Löschoption auszuführen:

<?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;
    }
}
3
Pierre Dumuid

Hier ist mein einfacher Ansatz.

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

So habe ich es gemacht:

Definieren Sie zuerst die Spaltennamen, die Sie verwenden, oder lassen Sie sie leer. In diesem Fall gehen Sie davon aus, dass Sie alle Spalten in der Tabelle verwenden möchten. In diesem Fall müssen Sie die Zeilenwerte in der genauen Reihenfolge angeben, in der sie in der Tabelle angezeigt werden .

$cols = 'name', 'middleName', 'eMail';
$table = 'people';

Angenommen, Sie haben bereits ein zweidimensionales Array vorbereitet. Iteriere es und konstruiere eine Zeichenkette mit deinen Zeilenwerten:

foreach ( $people as $person ) {
if(! $rowVals ) {
$rows = '(' . "'$name'" . ',' . "'$middleName'" . ',' .           "'$eMail'" . ')';
} else { $rowVals  = '(' . "'$name'" . ',' . "'$middleName'" . ',' . "'$eMail'" . ')';
}

Sie haben jetzt überprüft, ob $ rows bereits definiert ist. Wenn nicht, erstellen Sie sie und speichern Sie die Zeilenwerte und die erforderliche SQL-Syntax, um eine gültige Anweisung zu erhalten. Beachten Sie, dass Strings in doppelte Anführungszeichen und einfache Anführungszeichen gesetzt werden sollten, damit sie sofort als solche erkannt werden.

Jetzt müssen Sie nur noch die Anweisung vorbereiten und ausführen:

$stmt = $db->prepare ( "INSERT INTO $table $cols VALUES $rowVals" );
$stmt->execute ();

Getestet mit bis zu 2000 Reihen und die Ausführungszeit ist trostlos. Ich werde noch ein paar Tests durchführen und hierher zurückkommen, falls ich noch etwas beitragen kann.

Grüße.

1

Obwohl eine alte Frage alle Beiträge sehr geholfen hat, hier ist meine Lösung, die in meiner eigenen DbContext-Klasse funktioniert. Der $rows-Parameter ist einfach ein Array von assoziativen Arrays, die Zeilen oder Modelle darstellen: field name => insert value.

Wenn Sie ein Muster verwenden, das Modelle verwendet, passt dies gut, wenn Modelldaten als Array übergeben werden, z. B. von einer ToRowArray-Methode innerhalb der Modellklasse.

Hinweis : Es sollte selbstverständlich sein, aber die übergebenen Argumente niemals zulassen Um diese Methode für den Benutzer verfügbar zu machen oder auf andere Benutzereingaben als die Einfügewerte zu setzen, die validiert und bereinigt wurden. Das $tableName-Argument und die Spaltennamen sollten von der aufrufenden Logik definiert werden. Ein User-Modell könnte beispielsweise der Benutzertabelle zugeordnet werden, deren Spaltenliste den Mitgliedsfeldern des Modells zugeordnet ist.

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();
}
1
Lee

Da dies noch nicht vorgeschlagen wurde, bin ich ziemlich sicher, dass LOAD DATA INFILE immer noch der schnellste Weg ist, um Daten zu laden, da die Indizierung deaktiviert wird, alle Daten eingefügt werden und dann die Indizes wieder aktiviert werden - alles in einer einzigen Anforderung.

Das Speichern der Daten als csv sollte unter Berücksichtigung von fputcsv ziemlich trivial sein. MyISAM ist am schnellsten, aber in InnoDB gibt es immer noch große Leistung. Es gibt andere Nachteile, aber ich würde diesen Weg gehen, wenn Sie viele Daten einfügen und nicht unter 100 Zeilen stören.

1
avatarofhope2

Aufgrund meiner Experimente fand ich heraus, dass die mysql insert-Anweisung mit mehreren Wertzeilen in einer einzelnen Transaktion die schnellste ist.

Wenn die Daten jedoch zu groß sind, kann die Einstellung max_allowed_packet von mysql die Einfügung einer einzelnen Transaktion mit mehreren Wertzeilen einschränken. Daher schlagen die folgenden Funktionen fehl, wenn Daten vorhanden sind, die größer als die max_allowed_packet-Größe von mysql sind:

  1. singleTransactionInsertWithRollback
  2. singleTransactionInsertWithPlaceholders
  3. singleTransactionInsert

Das erfolgreichste Szenario zum Einfügen großer Datenmengen ist die Methode transactionSpeed, die jedoch mehr Zeit in Anspruch nimmt als die oben genannten Methoden. Um dieses Problem zu lösen, können Sie entweder Ihre Daten in kleinere Blöcke aufteilen und eine einzelne Transaktion mehrmals aufrufen oder die Ausführungsgeschwindigkeit mit der Methode transactionSpeed aufgeben.

Hier ist meine Forschung

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

Die Ergebnisse für 100.000 Einträge für eine Tabelle mit nur zwei Spalten sind wie folgt

$ 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
theBuzzyCoder

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

}
?>
0
sonofkrish

Ich hatte das gleiche Problem und dies ist, was ich für mich selbst erledige, und ich habe eine Funktion für mich selbst erstellt (und Sie können sie verwenden, wenn Ihnen das hilft).

Beispiel:

INSERT IN LÄNDER (Land, Stadt) WERTE (Deutschland, Berlin), (Frankreich, Paris);

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

WenninsertMultipleData ($ table, $ multi_params) TRUE zurückgibt, wurden Ihre Daten in Ihre Datenbank eingefügt.

0
Dardan

Hier ist meine Lösung: https://github.com/sasha-ch/Aura.Sql Basierend auf der auraphp/Aura.Sql-Bibliothek.

Anwendungsbeispiel:

$q = "insert into t2(id,name) values (?,?), ... on duplicate key update name=name"; 
$bind_values = [ [[1,'str1'],[2,'str2']] ];
$pdo->perform($q, $bind_values);

Fehlerberichte sind willkommen.

0
sasha-ch

Mein reales Beispiel zum Einfügen aller deutschen Postleitzahlen in eine leere Tabelle (um später Ortsnamen hinzuzufügen):

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

Wie Sie sehen, ist es völlig flexibel. Sie müssen nicht die Anzahl der Spalten oder die Position Ihrer Spalte überprüfen. Sie müssen nur die Einfügungsdaten festlegen:

    $row['postcode'] = sprintf('%05d', $postcode);

Ich bin stolz auf einige der Abfrage-String-Konstruktoren, da sie ohne umfangreiche Array-Funktionen wie array_merge funktionieren. Vor allem vsprintf () war ein guter Fund.

Schließlich musste ich 2x while () hinzufügen, um die Speichergrenze nicht zu überschreiten. Dies hängt von Ihrem Speicherlimit ab, ist aber generell eine gute Lösung, um Probleme zu vermeiden (und 10 Anfragen sind immer noch viel besser als 10.000).

0
mgutt

Das hat bei mir funktioniert 

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

Mit dieser Funktion können Sie mehrere Zeilen in eine einzelne Abfrage einfügen:

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 ist ein Array von Wertefeldern .. _ In Ihrem Fall würden Sie die Funktion mit aufrufen

insertMultiple("INSERT INTO tbl (`key1`,`key2`)",array(array('r1v1','r1v2'),array('r2v1','r2v2')));

Dies hat den Vorteil, dass Sie vorbereitete Anweisungen verwenden, während Sie mehrere Zeilen mit einer einzigen Abfrage einfügen. Sicherheit!

0