varios tablas statement relacionadas registros prepared multiples insertar ejemplos datos php pdo insert prepared-statement

tablas - php pdo



Preparado PDO Inserta múltiples filas en una sola consulta (19)

Aquí está mi enfoque 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();

Actualmente estoy usando este tipo de SQL en MySQL para insertar múltiples filas de valores en una sola consulta:

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

En las lecturas en PDO, el uso de declaraciones preparadas debería proporcionarme una mejor seguridad que las consultas estáticas.

Por lo tanto, me gustaría saber si es posible generar "insertar múltiples filas de valores mediante el uso de una consulta" usando declaraciones preparadas.

Si es así, ¿puedo saber cómo puedo implementarlo?


Aquí está mi solución: https://github.com/sasha-ch/Aura.Sql basado en la biblioteca auraphp / Aura.Sql.

Ejemplo de uso:

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

Los errores son bienvenidos.


Aquí hay una clase que escribí hacer varias inserciones con la opción de purga:

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


Así es como lo hice:

Primero defina los nombres de las columnas que usará o déjelos en blanco y pdo supondrá que desea usar todas las columnas de la tabla, en cuyo caso deberá informar los valores de las filas en el orden exacto en que aparecen en la tabla. .

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

Ahora, supongamos que tiene una matriz bidimensional ya preparada. Iterelo y construya una cadena con sus valores de fila, como tal:

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

Ahora, lo que acabas de hacer fue verificar si $ rows ya estaba definido, y si no, crearlo y almacenar los valores de fila y la sintaxis SQL necesaria para que sea una declaración válida. Tenga en cuenta que las cadenas deben ir dentro de comillas dobles y comillas simples, por lo que se reconocerán rápidamente como tales.

Todo lo que queda por hacer es preparar la declaración y ejecutar, como tal:

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

Probado con hasta 2000 filas hasta el momento, y el tiempo de ejecución es pésimo. Haré algunas pruebas más y volveré aquí en caso de que tenga algo más para contribuir.

Saludos.


Aunque es una vieja pregunta, todas las contribuciones me ayudaron mucho, así que aquí está mi solución, que funciona dentro de mi propia clase DbContext . El parámetro $rows es simplemente una matriz de matrices asociativas que representan filas o modelos: field name => insert value .

Si utiliza un patrón que utiliza modelos, esto encaja muy bien cuando pasa los datos del modelo como una matriz, por ejemplo, desde un método ToRowArray dentro de la clase de modelo.

Nota : No hace falta decirlo, pero nunca permita que los argumentos pasados ​​a este método se expongan al usuario ni dependan de ninguna entrada del usuario, aparte de los valores de inserción, que se hayan validado y desinfectado. El argumento $tableName y los nombres de columna deben ser definidos por la lógica de llamada; por ejemplo, un modelo de User podría asignarse a la tabla de usuarios, que tiene su lista de columnas asignada a los campos de miembros del modelo.

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


Como todavía no se ha sugerido, estoy bastante seguro de que LOAD DATA INFILE sigue siendo la forma más rápida de cargar datos, ya que deshabilita la indexación, inserta todos los datos y luego vuelve a habilitar los índices, todo en una sola solicitud.

Guardar los datos como un csv debería ser bastante trivial teniendo en cuenta fputcsv. MyISAM es el más rápido, pero aún obtiene un gran rendimiento en InnoDB. Sin embargo, hay otras desventajas, así que seguiría esta ruta si está insertando muchos datos y no se molesta con menos de 100 filas.


Dos posibles enfoques:

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

O:

$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 los datos para todas las filas están en una sola matriz, usaría la segunda solución.


Esa no es la manera en que usas declaraciones preparadas.

Está perfectamente bien insertar una fila por consulta porque puede ejecutar una instrucción preparada varias veces con diferentes parámetros. De hecho, esa es una de las mayores ventajas, ya que le permite insertar una gran cantidad de filas de manera eficiente, segura y cómoda.

Por lo tanto, tal vez sea posible implementar el esquema que propone, al menos para un número fijo de filas, pero está casi garantizado que esto no es realmente lo que desea.


Esto funcionó para mí

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


La mayoría de las soluciones proporcionadas aquí para crear la consulta preparada son más complejas de lo que deben ser. Usando las funciones integradas de PHP puedes crear fácilmente la declaración de SQL sin una sobrecarga significativa.

Dado $records , una matriz de registros donde cada registro es en sí mismo una matriz indexada (en forma de field => value ), la siguiente función insertará los registros en la tabla dada $table , en una conexión $connection PDO, usando solo una sola declaración preparada. Tenga en cuenta que esta es una solución PHP 5.6+ debido al uso de desempaquetado de argumentos en la llamada a array_push :

private function import(PDO $connection, $table, array $records) { $fields = array_keys($records[0]); $placeHolders = substr(str_repeat('',?'', count($fields)), 1); $values = []; foreach ($records as $record) { array_push($values, ...array_values($record)); } $query = ''INSERT INTO '' . $table . '' (''; $query .= implode('','', $fields); $query .= '') VALUES (''; $query .= implode(''),('', array_fill(0, count($records), $placeHolders)); $query .= '')''; $statement = $connection->prepare($query); $statement->execute($values); }


La misma respuesta que el Sr. Balagtas, un poco más clara ...

Las versiones recientes de MySQL y PHP PDO admiten INSERT múltiples filas.

Descripción general de SQL

El SQL se verá más o menos así, suponiendo que una tabla de 3 columnas le gustaría INSERT .

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

ON DUPLICATE KEY UPDATE funciona como se espera, incluso con un INSERT de varias filas; añada esto:

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

Descripción general de PHP

Su código PHP seguirá las habituales $pdo->prepare($qry) y $stmt->execute($params) .

$params será una matriz de 1 dimensión de todos los valores para pasar al INSERT .

En el ejemplo anterior, debe contener 9 elementos; PDO utilizará cada conjunto de 3 como una sola fila de valores. (Insertar 3 filas de 3 columnas cada una = matriz de 9 elementos).

Implementación

El siguiente código está escrito para mayor claridad, no para eficiencia. Trabaje con PHP array_*() funciones para mejores formas de asignar o recorrer sus datos si lo desea. Si puede usar transacciones obviamente depende de su tipo de tabla MySQL.

Asumiendo:

  • $tblName - el nombre de la cadena de la tabla para INSERTAR
  • $colNames - matriz unidimensional de los nombres de columna de la tabla Estos nombres de columna deben ser identificadores de columna MySQL válidos; escapar de ellos con palos de atrás (``) si no son
  • $dataVals - matriz $dataVals -dimensional, donde cada elemento es una matriz de 1-d de una fila de valores para INSERTAR

Código de muestra

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


La respuesta aceptada por Herbert Balagtas funciona bien cuando la matriz $ data es pequeña. Con las matrices de datos más grandes, la función array_merge se vuelve prohibitivamente lenta. Mi archivo de prueba para crear la matriz $ data tiene 28 cols y tiene alrededor de 80,000 líneas. El guión final tardó 41 segundos en completarse.

Utilizar array_push () para crear $ insert_values ​​en lugar de array_merge () dio como resultado una velocidad de 100X con un tiempo de ejecución de 0.41s .

La problemática array_merge ():

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

Para eliminar la necesidad de array_merge (), puede construir las siguientes dos matrices en su lugar:

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

Estas matrices se pueden usar de la siguiente manera:

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


La unión de matriz debe ser incluso más rápida que array_push , por lo que debe tener algo como:

$cumulativeArray += $rowArray;


Mi ejemplo del mundo real para insertar todos los códigos postales alemanes en una tabla vacía (para agregar nombres de ciudades más adelante):

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

Como puede ver, es completamente flexible. No necesita verificar la cantidad de columnas ni verificar en qué posición se encuentra su columna. Solo necesita configurar los datos de inserción:

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

Estoy orgulloso de algunos de los constructores de cadenas de consulta, ya que funcionan sin grandes funciones de matriz como array_merge. Especialmente vsprintf () fue un buen descubrimiento.

Finalmente, necesité agregar 2x while () para evitar exceder el límite de memoria. Esto depende de su límite de memoria, pero en general es una buena solución general para evitar problemas (y tener 10 consultas es mucho mejor que 10.000).


Por lo que vale, he visto a muchos usuarios recomendar iterar a través de instrucciones INSERT en lugar de construir como una sola cadena de consulta como lo hizo la respuesta seleccionada. Decidí ejecutar una prueba simple con solo dos campos y una instrucción de inserción muy básica:

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

Si bien la consulta general en sí misma tomó milisegundos o menos, la última consulta (cadena única) fue consistentemente 8 veces más rápida o más. Si esto se construyó para decir que refleja una importación de miles de filas en muchas más columnas, la diferencia podría ser enorme.


Puede insertar varias filas en una sola consulta con esta función:

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 es una matriz de matrices de valores. En tu caso llamarías a la función con

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

Esto tiene la ventaja de que usa declaraciones preparadas , al insertar varias filas con una sola consulta. ¡Seguridad!


Una respuesta más breve: aplanar la matriz de datos ordenados por columnas y luego

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

Al insertar un millar de registros o más, no desea tener que recorrer todos los registros para insertarlos cuando lo único que necesita es un recuento de los valores.


Insertar múltiples valores con declaraciones preparadas PDO

Insertar valores múltiples en una instrucción de ejecución. Por qué porque según esta página es más rápido que las inserciones regulares.

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

más valores de datos o probablemente tenga un bucle que rellene datos.

Con las inserciones preparadas, necesita saber los campos en los que está insertando y la cantidad de campos para crearlos. marcadores de posición para enlazar sus parámetros.

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

Esa es básicamente la forma en que queremos que se vea la instrucción de inserción.

Ahora, el código:

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

Aunque en mi prueba, hubo solo una diferencia de 1 segundo cuando se usan insertos múltiples e inserciones preparadas regularmente con un valor único.


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