web-dev-qa-db-fra.com

PDO Prepared Insère plusieurs lignes dans une requête unique

J'utilise actuellement ce type de SQL sur MySQL pour insérer plusieurs lignes de valeurs dans une seule requête:

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

Sur les lectures sur PDO, l'utilisation d'instructions préparées devrait me donner une meilleure sécurité que les requêtes statiques.

Je voudrais donc savoir s'il est possible de générer "l'insertion de plusieurs lignes de valeurs à l'aide d'une seule requête" à l'aide d'instructions préparées. 

Si oui, puis-je savoir comment puis-je le mettre en œuvre?

124
hoball

Valeurs multiples Insérer avec des instructions préparées PDO

Insertion de plusieurs valeurs dans une instruction d'exécution. Pourquoi parce que selon cette page elle est plus rapide que les inserts ordinaires. 

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

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

plus de valeurs de données ou vous avez probablement une boucle qui remplit les données.

Avec les encarts préparés, vous devez connaître les champs que vous insérez et le nombre de champs pour créer le? des espaces réservés pour lier vos paramètres.

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

C’est essentiellement ce à quoi nous voulons que l’insertion insert ressemble.

Maintenant, le 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();

Bien que dans mon test, il y avait seulement une différence de 1 seconde lorsque l'on utilisait plusieurs inserts et des inserts préparés avec une seule valeur.

129
Herbert Balagtas

Même réponse que M. Balagtas, un peu plus clair ...

Les versions récentes de MySQL et PHP PDO do prennent en charge les instructions INSERT sur plusieurs lignes.

Vue d'ensemble SQL

Le code SQL ressemblera à quelque chose comme ceci, en supposant une table à 3 colonnes dans laquelle vous voudriez INSERT.

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

ON DUPLICATE KEY UPDATE fonctionne comme prévu même avec un INSERT à plusieurs lignes; ajouter ceci:

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

Présentation de PHP

Votre code PHP suivra les appels PDO $pdo->prepare($qry) et $stmt->execute($params) habituels.

$params sera un tableau à 1 dimension de all les valeurs à transmettre à la INSERT.

Dans l'exemple ci-dessus, il devrait contenir 9 éléments. PDO utilisera chaque ensemble de 3 comme une seule ligne de valeurs. (Insertion de 3 lignes de 3 colonnes = un tableau de 9 éléments.)

La mise en oeuvre

Le code ci-dessous est écrit pour plus de clarté, pas d'efficacité. Utilisez les fonctions PHP array_*() pour de meilleurs moyens de mapper ou de parcourir vos données, si vous le souhaitez. Que vous puissiez utiliser des transactions dépend évidemment de votre type de table MySQL.

En supposant:

  • $tblName - le nom de chaîne de la table à INSERT to
  • $colNames - tableau à une dimension des noms de colonne de la table Ces noms de colonne doivent être des identificateurs de colonne MySQL valides. leur échapper avec des backticks (``) s'ils ne sont pas
  • $dataVals - tableau multidimensionnel, où chaque élément est un tableau 1-d d'une ligne de valeurs à INSERT

Exemple de code

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

Pour ce qui en vaut la peine, de nombreux utilisateurs ont recommandé de procéder à une itération au moyen d'instructions INSERT plutôt que de créer une requête avec une seule chaîne comme l'a fait la réponse sélectionnée. J'ai décidé de lancer un test simple avec seulement deux champs et une instruction insert très basique:

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

Alors que la requête globale elle-même prenait des millisecondes ou moins, la dernière requête (chaîne unique) était systématiquement 8 fois plus rapide ou plus. Si cela était conçu pour refléter une importation de milliers de lignes sur de nombreuses autres colonnes, la différence pourrait être énorme.

37
JM4

La réponse acceptée de Herbert Balagtas fonctionne bien lorsque le tableau $ data est petit. Avec des tableaux de données plus grands, la fonction array_merge devient extrêmement lente. Mon fichier de test pour créer le tableau $ data a 28 colonnes et environ 80 000 lignes. Le script final a pris 41s à compléter.

Utiliser array_Push () pour créer $ insert_values ​​au lieu de array_merge () a entraîné une accélération de 100X avec un temps d'exécution de 0.41s.

La problématique array_merge ():

$insert_values = array();

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

Pour éliminer la nécessité de array_merge (), vous pouvez créer les deux tableaux suivants:

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

Ces tableaux peuvent ensuite être utilisés comme suit:

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.

Deux approches possibles:

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

Ou:

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

Si les données pour toutes les lignes sont dans un seul tableau, j'utiliserais la deuxième solution.

13
Zyx

Ce n'est tout simplement pas la façon dont vous utilisez les déclarations préparées. 

Il est parfaitement correct d'insérer une ligne par requête car vous pouvez exécuter une instruction préparée plusieurs fois avec des paramètres différents. En fait, c’est l’un des plus grands avantages car il vous permet d’insérer un grand nombre de rangées de manière efficace, sécurisée et confortable.

Il est donc possible d’appliquer le schéma que vous proposez, au moins pour un nombre fixe de lignes, mais il est presque garanti que ce n’est pas vraiment ce que vous voulez.

12
sebasgo

Une réponse plus courte: aplatissez le tableau de données ordonné par colonnes puis

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

Lorsque vous insérez environ 1 000 enregistrements, vous ne voulez pas avoir à parcourir tous les enregistrements pour les insérer lorsque tout ce dont vous avez besoin est un nombre de valeurs.

7
fyrye

Voici une classe que j'ai écrite faire plusieurs insertions avec l'option de purge:

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

Voici mon approche simple.

    $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

Voici comment je l'ai fait:

Commencez par définir les noms de colonne que vous utiliserez ou laissez le champ vide et pdo supposera que vous souhaitez utiliser toutes les colonnes de la table. Dans ce cas, vous devez renseigner les valeurs de ligne dans l'ordre exact où elles apparaissent dans la table .

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

Supposons maintenant que vous ayez déjà préparé un tableau à deux dimensions. Itérez-le et construisez une chaîne avec vos valeurs de ligne, en tant que telle:

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

Maintenant, ce que vous venez de faire était de vérifier si $ row était déjà défini, et sinon, créez-le et stockez les valeurs de ligne et la syntaxe SQL nécessaire pour qu'il s'agisse d'une instruction valide. Notez que les chaînes doivent être placées dans des guillemets doubles et des guillemets simples, elles seront donc rapidement reconnues comme telles.

Il ne reste plus qu'à préparer la déclaration et à l'exécuter, en tant que telle:

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

Testé avec jusqu'à 2000 lignes jusqu'à présent et le temps d'exécution est lamentable. Fera d'autres tests et reviendra ici au cas où j'aurais quelque chose à ajouter.

Cordialement.

1
Théo T. Carranza

Bien qu’une vieille question, toutes les contributions m’aient beaucoup aidé, voici donc ma solution, qui fonctionne dans ma propre classe DbContext. Le paramètre $rows est simplement un tableau de tableaux associatifs représentant des lignes ou des modèles: field name => insert value.

Si vous utilisez un modèle qui utilise des modèles, cela convient parfaitement lorsque les données de modèle sont transmises sous forme de tableau, par exemple à partir d'une méthode ToRowArray de la classe de modèle.

Note : Cela devrait aller de soi mais ne jamais laisser les arguments être transmis à cette méthode pour être exposé à l'utilisateur ou dépendant de toute entrée d'utilisateur, autre que les valeurs d'insertion, qui ont été validées et vérifiées. L'argument $tableName et les noms de colonne doivent être définis par la logique d'appel. Par exemple, un modèle User peut être mappé sur la table utilisateur, dont la liste des colonnes est mappée sur les champs de membre du modèle.

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

Comme cela n’a pas encore été suggéré, je suis presque sûr que LOAD DATA INFILE est toujours le moyen le plus rapide de charger des données car il désactive l’indexation, insère toutes les données, puis réactive les index, le tout dans une seule requête.

Enregistrer les données en tant que csv devrait être assez simple en gardant à l’esprit fputcsv. MyISAM est le plus rapide, mais vous obtenez toujours de grandes performances dans InnoDB. Il y a d'autres inconvénients, cependant, je choisirais cette voie si vous insérez beaucoup de données et ne vous embêtez pas avec moins de 100 lignes.

1
avatarofhope2

Sur la base de mes expériences, j'ai découvert que l'instruction mysql insert avec plusieurs lignes de valeur dans une seule transaction est la plus rapide.

Cependant, si les données sont trop nombreuses, le paramètre max_allowed_packet de mysql peut restreindre l'insertion de transaction unique avec plusieurs lignes de valeur. Par conséquent, les fonctions suivantes échoueront s'il existe des données supérieures à la taille de max_allowed_packet de mysql:

  1. singleTransactionInsertWithRollback
  2. singleTransactionInsertWithPlaceholders
  3. singleTransactionInsert

Le scénario le plus réussi en insertion de données volumineuses est la méthode transactionSpeed, mais il consomme plus de temps que les méthodes susmentionnées. Donc, pour gérer ce problème, vous pouvez soit fractionner vos données en fragments plus petits et appeler plusieurs fois une seule transaction, soit abandonner la vitesse d’exécution en utilisant la méthode transactionSpeed.

Voici mes recherches

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

Les résultats pour 100 000 entrées pour une table contenant seulement deux colonnes sont comme ci-dessous

$ 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

J'ai eu le même problème et c'est comme ça que je me débrouille, et je me suis créé une fonction pour ça (et vous pouvez l'utiliser si cela vous aide).

Exemple:

INSCRIRE DANS les pays (pays, ville) VALEURS (Allemagne, Berlin), (France, 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;
    }

SiinsertMultipleData ($ table, $ multi_params) renvoie VRAI , vos données ont été insérées dans votre base de données.

0
Dardan

Voici ma solution: https://github.com/sasha-ch/Aura.Sql Basé sur la bibliothèque auraphp/Aura.Sql.

Exemple d'utilisation:

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

Les rapports de bug sont les bienvenus.

0
sasha-ch

Mon exemple du monde réel pour insérer tous les codes postaux allemands dans un tableau vide (pour ajouter des noms de ville plus tard):

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

Comme vous pouvez le voir, il est totalement flexible. Vous n'avez pas besoin de vérifier le nombre de colonnes ni de vérifier la position de votre colonne. Il vous suffit de définir les données d'insertion:

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

Je suis fier de certains constructeurs de chaînes de requête car ils fonctionnent sans fonctions de tableau lourdes telles que array_merge. En particulier, vsprintf () était une bonne trouvaille.

Enfin, je devais ajouter 2x while () pour éviter de dépasser la limite de mémoire. Cela dépend de votre limite de mémoire mais c'est en tout cas une bonne solution générale pour éviter les problèmes (et avoir 10 requêtes est toujours bien meilleur que 10.000).

0
mgutt

Cela a fonctionné pour moi 

    $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

Vous pouvez insérer plusieurs lignes dans une même requête avec cette fonction:

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 est un tableau de tableaux de valeurs . Dans votre cas, vous appelleriez la fonction avec

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

Cela présente l'avantage que vous utilisez les instructions préparées, lors de l'insertion de plusieurs lignes avec une seule requête. Sécurité!

0