inserte varias filas a través de una matriz php en mysql

129

Estoy pasando un gran conjunto de datos a una tabla MySQL a través de PHP usando comandos de inserción y me pregunto si es posible insertar aproximadamente 1000 filas a la vez a través de una consulta que no sea agregar cada valor al final de una cadena de una milla de largo y luego ejecutándolo Estoy usando el marco CodeIgniter, por lo que sus funciones también están disponibles para mí.

toofarsideways
fuente
He respondido de acuerdo con su pregunta para la inserción de múltiples filas de Codeigniter.
Somnath Muluk
@SomnathMuluk Gracias, sin embargo, ha pasado un tiempo desde que necesité responder esta pregunta
:)
Recomendaría usar la función insert_batch de CodeIgniter. Si usa una biblioteca, siempre trate de aprovechar sus fortalezas y estándares de codificación.
Dewald Els
Creo que insertar lote es la mejor manera de hacerlo, vea el enlace stackoverflow.com/questions/27206178/codeigniter-insert-batch
Syed Amir Bukhari

Respuestas:

234

Ensamblar una INSERTdeclaración con varias filas es mucho más rápido en MySQL que una INSERTdeclaración por fila.

Dicho esto, parece que es posible que se encuentre con problemas de manejo de cadenas en PHP, que en realidad es un problema de algoritmo, no de lenguaje. Básicamente, cuando trabaja con cadenas grandes, desea minimizar las copias innecesarias. Principalmente, esto significa que desea evitar la concatenación. La forma más rápida y eficiente en cuanto a memoria para construir una cadena grande, como para insertar cientos de filas en una, es aprovechar la implode()función y la asignación de matriz.

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

La ventaja de este enfoque es que no copia y vuelve a copiar la instrucción SQL que ha reunido hasta ahora con cada concatenación; en cambio, PHP hace esto una vez en la implode()declaración. Esta es una gran victoria.

Si tiene muchas columnas para armar, y una o más son muy largas, también podría construir un bucle interno para hacer lo mismo y usar implode()para asignar la cláusula de valores a la matriz externa.

staticsan
fuente
55
¡Gracias por eso! Por cierto, falta un corchete de cierre al final de la función si alguien planea copiarlo. mysql_real_query ('INSERTAR EN LOS VALORES de la tabla (texto, categoría)' .implode (','. $ sql));
toofarsideways
3
¡Gracias! Fijo. (A menudo hago eso ...)
staticsan
1
y la consulta realmente debería ser 'INSERTAR EN LA tabla (texto, categoría) VALORES' .implode (','. $ sql) 'suspiro La codificación de las 4am conduce a una depuración terrible :(
toofarsideways
3
Creo que este código creará una solución para mi último proyecto. Mi pregunta aquí es, ¿está a salvo de la inyección de SQL? Mis planes son para cambiar a cabo mysql_real_escape_stringcon mysqli_real_escape_stringy mysql_querycon el mysqli_queryque estoy usando MySQLi y éstos han de utilizarse a partir de PHP5. ¡Muchas gracias!
wordman
2
mysql_*se ha eliminado de PHP, así que asegúrese de usar la mysqli_*interfaz.
Rick James el
60

La inserción múltiple / inserción por lotes ahora es compatible con codeigniter. Tuve el mismo problema Aunque es muy tarde para responder una pregunta, ayudará a alguien. Es por eso que responde esta pregunta.

$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
fuente
2
Creo que esta es la forma más recomendada de hacer una inserción de varias filas en lugar de usar mysql_query. Porque cuando usamos un marco es una buena práctica usar siempre las características integradas del marco.
Praneeth Nidarshan
22

Puede preparar la consulta para insertar una fila utilizando la clase mysqli_stmt y luego iterar sobre la matriz de datos. Algo como:

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

Donde 'idsb' son los tipos de datos que está vinculando (int, double, string, blob).

Espresso_Boy
fuente
66
Recientemente ejecuté algunos puntos de referencia que comparaban las instrucciones de inserción masiva y de inserción preparada como se menciona aquí. Para alrededor de 500 insertos, el método de insertos preparado se completó entre 2.6-4.4 segundos, y el método de inserto a granel en 0.12-0.35 segundos. Pensé que mysql habría "agrupado" estas declaraciones preparadas juntas y funcionaría tan bien como las inserciones masivas, pero en una configuración predeterminada, la diferencia de rendimiento es enorme aparentemente. (Por cierto, todas las consultas de referencia se ejecutaron dentro de una sola transacción para cada prueba, a fin de evitar la confirmación automática)
Motin
16

Sé que esta es una consulta antigua, pero estaba leyendo y pensé que agregaría lo que encontré en otro lugar:

mysqli en PHP 5 es un ojbect con algunas buenas funciones que le permitirán acelerar el tiempo de inserción para la respuesta anterior:

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

Desactivar la confirmación automática al insertar muchas filas acelera enormemente la inserción, así que desactívela, luego ejecute como se mencionó anteriormente, o simplemente haga una cadena (sqlCombined) que es muchas instrucciones de inserción separadas por punto y coma y consultas múltiples las manejará bien.

Espero que esto ayude a alguien a ahorrar tiempo (¡buscar e insertar!)

R

Ross Carver
fuente
Este es el error que obtuve al usar su idea: "Error grave: Llamar a una función miembro autocommit () en nulo en /homepages/25/d402746174/htdocs/MoneyMachine/saveQuotes.php en la línea 30"
usuario3217883
8

Siempre puedes usar mysql's LOAD DATA:

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

hacer inserciones masivas en lugar de usar un montón de INSERTdeclaraciones.

vezult
fuente
Lo había investigado pero necesito manipular los datos antes de insertarlos. Me lo han dado como producto cartesiano de un conjunto de valores int de 1400 por 1400, muchos de los cuales son cero. Necesito convertir que a una relación de muchos a muchos mediante una tabla de intermediario para ahorrar espacio, por tanto, la necesidad de inserciones en oposición a una inserción masiva
toofarsideways
Siempre puede generar un archivo csv después de manipularlo y llamar a la declaración mysql que carga los datos
Alexander Jardim
Creo que es útil saber que la ruta es local para su cliente SQL, y no en el servidor SQL. El archivo se carga en el servidor y luego lo lee. Pensé que el archivo ya tenía que estar en el servidor, que no es el caso. Si ya está en el servidor, elimine el LOCALbit.
Kyle
5

Bueno, no desea ejecutar 1000 llamadas de consulta, pero hacer esto está bien:

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

Dependiendo de su fuente de datos, llenar la matriz podría ser tan fácil como abrir un archivo y descargar el contenido en una matriz a través de file().

bdl
fuente
1
Es más limpio si mueve ese if encima de la consulta y lo cambia a algo como if ($ k> 0).
cherouvim
@cherouvim ... Bueno, tienes razón en eso. Gracias por tu contribución. Mientras vuelvo a leer el ejemplo que proporcioné, no veo su punto. Cuidado para elaborar (a través de pastebin, etc?). Gracias
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
fuente
1

Puede hacerlo de varias maneras en codeigniter, por ejemplo

Primero por bucle

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

Segundo : por lote de inserción

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

Tercera forma: por pase de valor múltiple

$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
fuente
1

Aunque es demasiado tarde para responder esta pregunta. Aquí está mi respuesta sobre lo mismo.

Si está usando CodeIgniter, puede usar métodos incorporados definidos en la clase query_builder.

$ this-> db-> insert_batch ()

Genera una cadena de inserción basada en los datos que proporciona y ejecuta la consulta. Puede pasar una matriz o un objeto a la función. Aquí hay un ejemplo usando una matriz:

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

El primer parámetro contendrá el nombre de la tabla, el segundo es una matriz asociativa de valores.

Puede encontrar más detalles sobre query_builder aquí

Abhishek Singh
fuente
0

He creado una clase que realiza varias líneas que se utiliza de la siguiente manera:

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

donde la clase se define de la siguiente manera:

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;
    }
}
usuario3682438
fuente
0

Use insertar lote en codeigniter para insertar varias filas de datos.

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

He creado esta función simple que ustedes pueden usar fácilmente. Deberá pasar el nombre de la ($tbl)tabla, el campo de la tabla ($insertFieldsArr)contra sus datos de inserción, la matriz de datos ($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
fuente