insérer plusieurs lignes via un tableau php dans mysql

129

Je passe un grand ensemble de données dans une table MySQL via PHP à l'aide de commandes d'insertion et je me demande s'il est possible d'insérer environ 1000 lignes à la fois via une requête autre que d'ajouter chaque valeur à la fin d'une chaîne d'un kilomètre de long, puis l'exécuter. J'utilise le framework CodeIgniter donc ses fonctions sont également disponibles pour moi.

trop loin
la source
J'ai donné une réponse en fonction de votre question pour l'insert à plusieurs lignes de Codeigniter.
Somnath Muluk
@SomnathMuluk Merci, mais cela fait un moment que je n'ai pas eu besoin de répondre à cette question
:)
Je recommanderais d'utiliser la fonction insert_batch de CodeIgniter. Si vous utilisez une bibliothèque, essayez toujours de tirer parti de ses atouts et de ses normes de codage.
Dewald Els
Je pense que l'insertion d'un lot est le meilleur moyen de le faire.Voir
Syed Amir Bukhari

Réponses:

234

L'assemblage d'une INSERTinstruction avec plusieurs lignes est beaucoup plus rapide dans MySQL qu'une INSERTinstruction par ligne.

Cela dit, il semble que vous rencontriez peut-être des problèmes de gestion des chaînes en PHP, ce qui est vraiment un problème d'algorithme, pas un problème de langage. Fondamentalement, lorsque vous travaillez avec de grandes chaînes, vous voulez minimiser les copies inutiles. Cela signifie principalement que vous souhaitez éviter la concaténation. Le moyen le plus rapide et le plus efficace en termes de mémoire pour créer une chaîne volumineuse, par exemple pour insérer des centaines de lignes en une, consiste à tirer parti de l' implode()affectation des fonctions et des tableaux.

$sql = array(); 
foreach( $data as $row ) {
    $sql[] = '("'.mysql_real_escape_string($row['text']).'", '.$row['category_id'].')';
}
mysql_query('INSERT INTO table (text, category) VALUES '.implode(',', $sql));

L'avantage de cette approche est que vous ne copiez pas et ne recopiez pas l'instruction SQL que vous avez jusqu'à présent assemblée avec chaque concaténation; à la place, PHP le fait une fois dans l' implode()instruction. C'est une grande victoire.

Si vous avez beaucoup de colonnes à assembler et qu'une ou plusieurs sont très longues, vous pouvez également créer une boucle interne pour faire la même chose et l'utiliser implode()pour affecter la clause values ​​au tableau externe.

statique
la source
5
Merci pour ça! Btw votre manque un crochet fermant à la fin de la fonction si quelqu'un envisage de le copier. mysql_real_query ('INSERT INTO table VALUES (texte, catégorie)' .implode (','. $ sql));
toofarsideways
3
Merci! Fixé. (Je fais souvent ça ...)
staticsan
1
et la requête devrait vraiment être 'INSERT INTO table (text, category) VALUES' .implode (','. $ sql) 'soupir 4am le codage conduit à un débogage terrible :(
toofarsideways
3
Je pense que ce code créera une solution pour mon dernier projet. Ma question est la suivante: est-ce à l'abri de l'injection SQL? Mes projets sont de changer mysql_real_escape_stringavec mysqli_real_escape_stringet mysql_queryavec mysqli_querycar j'utilise MySQLi et ceux-ci sont obsolètes à partir de PHP5. Merci beaucoup!
wordman
2
mysql_*a été supprimé de PHP, assurez-vous donc d'utiliser l' mysqli_*interface.
Rick James
60

L'insertion multiple / par lots est désormais prise en charge par codeigniter. J'ai eu le même problème. Bien qu'il soit très tard pour répondre à la question, cela aidera quelqu'un. C'est pourquoi répondre à cette question.

$data = array(
   array(
      'title' => 'My title' ,
      'name' => 'My Name' ,
      'date' => 'My date'
   ),
   array(
      'title' => 'Another title' ,
      'name' => 'Another Name' ,
      'date' => 'Another date'
   )
);

$this->db->insert_batch('mytable', $data);

// Produces: INSERT INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date'), ('Another title', 'Another name', 'Another date')
Somnath Muluk
la source
2
Je pense que c'est la manière la plus recommandée de faire une insertion multi-lignes plutôt que d'utiliser mysql_query. Parce que lorsque nous utilisons un framework, il est recommandé de toujours utiliser les fonctionnalités intégrées du framework.
Praneeth Nidarshan
22

Vous pouvez préparer la requête pour insérer une ligne à l'aide de la classe mysqli_stmt, puis effectuer une itération sur le tableau de données. Quelque chose comme:

$stmt =  $db->stmt_init();
$stmt->prepare("INSERT INTO mytbl (fld1, fld2, fld3, fld4) VALUES(?, ?, ?, ?)");
foreach($myarray as $row)
{
    $stmt->bind_param('idsb', $row['fld1'], $row['fld2'], $row['fld3'], $row['fld4']);
    $stmt->execute();
}
$stmt->close();

Où «idsb» correspond aux types de données que vous liez (int, double, string, blob).

Espresso_Boy
la source
6
J'ai récemment exécuté quelques tests comparant les instructions d'insertion en bloc et les instructions d'insertion préparées comme mentionné ici. Pour environ 500 inserts, la méthode des inserts préparés s'est terminée en 2,6 à 4,4 secondes et la méthode des inserts en vrac en 0,12 à 0,35 secondes. J'aurais pensé que mysql aurait "regroupé" ces instructions préparées ensemble et fonctionnerait aussi bien que les insertions en masse, mais dans une configuration par défaut, la différence de performance est apparemment énorme. (Btw, toutes les requêtes de référence s'exécutaient dans une seule transaction pour chaque test, afin d'éviter la validation automatique)
Motin
16

Je sais que c'est une vieille requête, mais je lisais juste et j'ai pensé ajouter ce que j'ai trouvé ailleurs:

mysqli en PHP 5 est un ojbect avec quelques bonnes fonctions qui vous permettront d'accélérer le temps d'insertion de la réponse ci-dessus:

$mysqli->autocommit(FALSE);
$mysqli->multi_query($sqlCombined);
$mysqli->autocommit(TRUE);

La désactivation de l'autocommit lors de l'insertion de nombreuses lignes accélère considérablement l'insertion, alors désactivez-la, puis exécutez-la comme mentionné ci-dessus, ou créez simplement une chaîne (sqlCombined) qui comprend de nombreuses instructions d'insertion séparées par des points-virgules et les requêtes multiples les géreront correctement.

J'espère que cela aidera quelqu'un à gagner du temps (recherche et insertion!)

R

Ross Carver
la source
Voici l'erreur que j'ai eue en utilisant votre idée: "Erreur fatale: Appel à une fonction membre autocommit () sur null dans /homepages/25/d402746174/htdocs/MoneyMachine/saveQuotes.php sur la ligne 30"
user3217883
8

Vous pouvez toujours utiliser mysql LOAD DATA:

LOAD DATA LOCAL INFILE '/full/path/to/file/foo.csv' INTO TABLE `footable` FIELDS TERMINATED BY ',' LINES TERMINATED BY '\r\n' 

pour faire des insertions en masse plutôt que d'utiliser un tas d' INSERTinstructions.

vezult
la source
J'avais examiné cela mais je dois manipuler les données avant de les insérer. Il m'a été donné comme un produit cartésien d'un ensemble de 1400 par 1400 de valeurs int, dont beaucoup sont nulles. J'ai besoin de convertir cela en une relation plusieurs à plusieurs en utilisant une table intermédiaire pour économiser de l'espace, d'où le besoin d'inserts par opposition à une insertion en vrac
toofarsideways
Vous pouvez toujours générer un fichier csv après l'avoir manipulé et appelé l'instruction mysql qui charge les données
Alexander Jardim
Je pense qu'il est utile de savoir que le chemin est local sur votre client SQL, et non sur le serveur SQL. Le fichier est téléchargé sur le serveur, puis lu par celui-ci. Je pensais que le fichier devait déjà être sur le serveur, ce qui n'est pas le cas. S'il est déjà sur le serveur, supprimez le LOCALbit.
Kyle
5

Eh bien, vous ne voulez pas exécuter 1000 appels de requête, mais cela convient parfaitement:

$stmt= array( 'array of statements' );
$query= 'INSERT INTO yourtable (col1,col2,col3) VALUES ';
foreach( $stmt AS $k => $v ) {
  $query.= '(' .$v. ')'; // NOTE: you'll have to change to suit
  if ( $k !== sizeof($stmt)-1 ) $query.= ', ';
}
$r= mysql_query($query);

Selon votre source de données, remplir le tableau peut être aussi simple que d'ouvrir un fichier et de vider le contenu dans un tableau via file().

bdl
la source
1
C'est plus propre si vous déplacez cela si au-dessus de la requête et le changez en quelque chose comme si ($ k> 0).
cherouvim
@cherouvim ... Eh bien, vous avez raison. Merci pour votre contribution. En relisant l'exemple que j'ai fourni, je ne comprends pas votre point de vue. Attention à élaborer (via pastebin, etc?). Thanks-
bdl
3
$query= array(); 
foreach( $your_data as $row ) {
    $query[] = '("'.mysql_real_escape_string($row['text']).'", '.$row['category_id'].')';
}
mysql_query('INSERT INTO table (text, category) VALUES '.implode(',', $query));
Nikunj Dhimar
la source
1

Vous pouvez le faire de plusieurs façons dans codeigniter, par exemple

Première par boucle

foreach($myarray as $row)
{
   $data = array("first"=>$row->first,"second"=>$row->sec);
   $this->db->insert('table_name',$data);
}

Deuxième - Par lot d'insertion

$data = array(
       array(
          'first' => $myarray[0]['first'] ,
          'second' => $myarray[0]['sec'],
        ),
       array(
          'first' => $myarray[1]['first'] ,
          'second' => $myarray[1]['sec'],
        ),
    );

    $this->db->insert_batch('table_name', $data);

Troisième voie - Par passage de valeurs multiples

$sql = array(); 
foreach( $myarray as $row ) {
    $sql[] = '("'.mysql_real_escape_string($row['first']).'", '.$row['sec'].')';
}
mysql_query('INSERT INTO table (first, second) VALUES '.implode(',', $sql));
Kumar Rakesh
la source
1

Bien qu'il soit trop tard pour répondre à cette question. Voici ma réponse sur le même.

Si vous utilisez CodeIgniter, vous pouvez utiliser des méthodes intégrées définies dans la classe query_builder.

$ this-> db-> insert_batch ()

Génère une chaîne d'insertion basée sur les données que vous fournissez et exécute la requête. Vous pouvez passer un tableau ou un objet à la fonction. Voici un exemple utilisant un tableau:

$data = array(
    array(
            'title' => 'My title',
            'name' => 'My Name',
            'date' => 'My date'
    ),
    array(
            'title' => 'Another title',
            'name' => 'Another Name',
            'date' => 'Another date'
    )

);

$this->db->insert_batch('mytable', $data);
// Produces: INSERT INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date'),  ('Another title', 'Another name', 'Another date')

Le premier paramètre contiendra le nom de la table, le second est un tableau associatif de valeurs.

Vous pouvez trouver plus de détails sur query_builder ici

Abhishek Singh
la source
0

J'ai créé une classe qui exécute plusieurs lignes qui est utilisée comme suit:

$pdo->beginTransaction();
$pmi = new PDOMultiLineInserter($pdo, "foo", array("a","b","c","e"), 10);
$pmi->insertRow($data);
// ....
$pmi->insertRow($data);
$pmi->purgeRemainingInserts();
$pdo->commit();

où la classe est définie comme suit:

class PDOMultiLineInserter {
    private $_purgeAtCount;
    private $_bigInsertQuery, $_singleInsertQuery;
    private $_currentlyInsertingRows  = array();
    private $_currentlyInsertingCount = 0;
    private $_numberOfFields;
    private $_error;
    private $_insertCount = 0;

    /**
     * Create a PDOMultiLine Insert object.
     *
     * @param PDO $pdo              The PDO connection
     * @param type $tableName       The table name
     * @param type $fieldsAsArray   An array of the fields being inserted
     * @param type $bigInsertCount  How many rows to collect before performing an insert.
     */
    function __construct(PDO $pdo, $tableName, $fieldsAsArray, $bigInsertCount = 100) {
        $this->_numberOfFields = count($fieldsAsArray);
        $insertIntoPortion = "REPLACE 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;
    }
}
user3682438
la source
0

Utilisez insérer un lot dans codeigniter pour insérer plusieurs lignes de données.

$this->db->insert_batch('tabname',$data_array); // $data_array holds the value to be inserted
aish
la source
0

J'ai créé cette fonction simple que vous pouvez utiliser facilement. Vous devrez passer le nom de la ($tbl)table, le champ de la table par ($insertFieldsArr)rapport à vos données d'insertion, tableau de données ($arr).

insert_batch('table',array('field1','field2'),$dataArray);

    function insert_batch($tbl,$insertFieldsArr,$arr){ $sql = array(); 
    foreach( $arr as $row ) {
        $strVals='';
        $cnt=0;
        foreach($insertFieldsArr as $key=>$val){
            if(is_array($row)){
                $strVals.="'".mysql_real_escape_string($row[$cnt]).'\',';
            }
            else{
                $strVals.="'".mysql_real_escape_string($row).'\',';
            }
            $cnt++;
        }
        $strVals=rtrim($strVals,',');
        $sql[] = '('.$strVals.')';
    }

    $fields=implode(',',$insertFieldsArr);
    mysql_query('INSERT INTO `'.$tbl.'` ('.$fields.') VALUES '.implode(',', $sql));
}
Waqas
la source