Related
Say, we have multiple rows to be inserted in a table:
$rows = [(1,2,3), (4,5,6), (7,8,9) ... ] //[ array of values ];
Using PDO:
$sql = "insert into `table_name` (col1, col2, col3) values (?, ?, ?)" ;
Now, how should you proceed in inserting the rows? Like this?
$stmt = $db->prepare($sql);
foreach($rows as $row){
$stmt->execute($row);
}
or, like this?
$sql = "insert into `table_name` (col1, col2, col3) values ";
$sql .= //not sure the best way to concatenate all the values, use implode?
$db->prepare($sql)->execute();
Which way would be faster and safer? What is the best way to insert multiple rows?
You have at least these two options:
$rows = [(1,2,3), (4,5,6), (7,8,9) ... ];
$sql = "insert into `table_name` (col1, col2, col3) values (?,?,?)";
$stmt = $db->prepare($sql);
foreach($rows as $row)
{
$stmt->execute($row);
}
OR:
$rows = [(1,2,3), (4,5,6), (7,8,9) ... ];
$sql = "insert into `table_name` (col1, col2, col3) values ";
$paramArray = array();
$sqlArray = array();
foreach($rows as $row)
{
$sqlArray[] = '(' . implode(',', array_fill(0, count($row), '?')) . ')';
foreach($row as $element)
{
$paramArray[] = $element;
}
}
// $sqlArray will look like: ["(?,?,?)", "(?,?,?)", ... ]
// Your $paramArray will basically be a flattened version of $rows.
$sql .= implode(',', $sqlArray);
$stmt = $db->prepare($sql);
$stmt->execute($paramArray);
As you can see the first version features a lot simpler code; however the second version does execute a batch insert. The batch insert should be faster, but I agree with #BillKarwin that the performance difference will not be noticed in the vast majority of implementations.
I would do it the first way, prepare the statement with one row of parameter placeholders, and insert one row at a time with execute.
$stmt = $db->prepare($sql);
foreach($rows as $row){
$stmt-> execute($row);
}
It's not quite as fast as doing multiple rows in a single insert, but it's close enough that you will probably never notice the difference.
And this has the advantage that it's very easy to work with the code. That's why you're using PHP anyway, for the developer efficiency, not the runtime efficiency.
If you have many rows (hundreds or thousands), and performance is a priority, you should consider using LOAD DATA INFILE.
You can also go this way:
<?php
$qmarks = '(?,?,?)'. str_repeat(',(?,?,?)', count($rows)-1);
$sql = "INSERT INTO `table`(col1,col2,col3) VALUES $qmarks";
$vals = array();
foreach($rows as $row)
$vals = array_merge($vals, $row);
$db->prepare($sql)->execute($vals);
To be honest, I don't know which one will be faster, all depends on the delay between mysql and the php server.
/* 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);
}
}
?>
The above code should be good solution for Inserting Multiple Records using PDO.
Say, we have multiple rows to be inserted in a table:
$rows = [(1,2,3), (4,5,6), (7,8,9) ... ] //[ array of values ];
Using PDO:
$sql = "insert into `table_name` (col1, col2, col3) values (?, ?, ?)" ;
Now, how should you proceed in inserting the rows? Like this?
$stmt = $db->prepare($sql);
foreach($rows as $row){
$stmt->execute($row);
}
or, like this?
$sql = "insert into `table_name` (col1, col2, col3) values ";
$sql .= //not sure the best way to concatenate all the values, use implode?
$db->prepare($sql)->execute();
Which way would be faster and safer? What is the best way to insert multiple rows?
You have at least these two options:
$rows = [(1,2,3), (4,5,6), (7,8,9) ... ];
$sql = "insert into `table_name` (col1, col2, col3) values (?,?,?)";
$stmt = $db->prepare($sql);
foreach($rows as $row)
{
$stmt->execute($row);
}
OR:
$rows = [(1,2,3), (4,5,6), (7,8,9) ... ];
$sql = "insert into `table_name` (col1, col2, col3) values ";
$paramArray = array();
$sqlArray = array();
foreach($rows as $row)
{
$sqlArray[] = '(' . implode(',', array_fill(0, count($row), '?')) . ')';
foreach($row as $element)
{
$paramArray[] = $element;
}
}
// $sqlArray will look like: ["(?,?,?)", "(?,?,?)", ... ]
// Your $paramArray will basically be a flattened version of $rows.
$sql .= implode(',', $sqlArray);
$stmt = $db->prepare($sql);
$stmt->execute($paramArray);
As you can see the first version features a lot simpler code; however the second version does execute a batch insert. The batch insert should be faster, but I agree with #BillKarwin that the performance difference will not be noticed in the vast majority of implementations.
I would do it the first way, prepare the statement with one row of parameter placeholders, and insert one row at a time with execute.
$stmt = $db->prepare($sql);
foreach($rows as $row){
$stmt-> execute($row);
}
It's not quite as fast as doing multiple rows in a single insert, but it's close enough that you will probably never notice the difference.
And this has the advantage that it's very easy to work with the code. That's why you're using PHP anyway, for the developer efficiency, not the runtime efficiency.
If you have many rows (hundreds or thousands), and performance is a priority, you should consider using LOAD DATA INFILE.
You can also go this way:
<?php
$qmarks = '(?,?,?)'. str_repeat(',(?,?,?)', count($rows)-1);
$sql = "INSERT INTO `table`(col1,col2,col3) VALUES $qmarks";
$vals = array();
foreach($rows as $row)
$vals = array_merge($vals, $row);
$db->prepare($sql)->execute($vals);
To be honest, I don't know which one will be faster, all depends on the delay between mysql and the php server.
/* 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);
}
}
?>
The above code should be good solution for Inserting Multiple Records using PDO.
Say, we have multiple rows to be inserted in a table:
$rows = [(1,2,3), (4,5,6), (7,8,9) ... ] //[ array of values ];
Using PDO:
$sql = "insert into `table_name` (col1, col2, col3) values (?, ?, ?)" ;
Now, how should you proceed in inserting the rows? Like this?
$stmt = $db->prepare($sql);
foreach($rows as $row){
$stmt->execute($row);
}
or, like this?
$sql = "insert into `table_name` (col1, col2, col3) values ";
$sql .= //not sure the best way to concatenate all the values, use implode?
$db->prepare($sql)->execute();
Which way would be faster and safer? What is the best way to insert multiple rows?
You have at least these two options:
$rows = [(1,2,3), (4,5,6), (7,8,9) ... ];
$sql = "insert into `table_name` (col1, col2, col3) values (?,?,?)";
$stmt = $db->prepare($sql);
foreach($rows as $row)
{
$stmt->execute($row);
}
OR:
$rows = [(1,2,3), (4,5,6), (7,8,9) ... ];
$sql = "insert into `table_name` (col1, col2, col3) values ";
$paramArray = array();
$sqlArray = array();
foreach($rows as $row)
{
$sqlArray[] = '(' . implode(',', array_fill(0, count($row), '?')) . ')';
foreach($row as $element)
{
$paramArray[] = $element;
}
}
// $sqlArray will look like: ["(?,?,?)", "(?,?,?)", ... ]
// Your $paramArray will basically be a flattened version of $rows.
$sql .= implode(',', $sqlArray);
$stmt = $db->prepare($sql);
$stmt->execute($paramArray);
As you can see the first version features a lot simpler code; however the second version does execute a batch insert. The batch insert should be faster, but I agree with #BillKarwin that the performance difference will not be noticed in the vast majority of implementations.
I would do it the first way, prepare the statement with one row of parameter placeholders, and insert one row at a time with execute.
$stmt = $db->prepare($sql);
foreach($rows as $row){
$stmt-> execute($row);
}
It's not quite as fast as doing multiple rows in a single insert, but it's close enough that you will probably never notice the difference.
And this has the advantage that it's very easy to work with the code. That's why you're using PHP anyway, for the developer efficiency, not the runtime efficiency.
If you have many rows (hundreds or thousands), and performance is a priority, you should consider using LOAD DATA INFILE.
You can also go this way:
<?php
$qmarks = '(?,?,?)'. str_repeat(',(?,?,?)', count($rows)-1);
$sql = "INSERT INTO `table`(col1,col2,col3) VALUES $qmarks";
$vals = array();
foreach($rows as $row)
$vals = array_merge($vals, $row);
$db->prepare($sql)->execute($vals);
To be honest, I don't know which one will be faster, all depends on the delay between mysql and the php server.
/* 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);
}
}
?>
The above code should be good solution for Inserting Multiple Records using PDO.
I stupidly built my web application with mysqli. Now, I'm trying to convert my data abstraction layer to pdo, but for some reason the insert query is giving me trouble. my shortcut insert function is called from the controller, and I was hoping to keep it in the name format with the table name and column/values array as the parameters.
I commented where I think the problem is below. Please help.
function insert($table, array $columns_values) {
// connect to db
$dbh = $this->db_connect();
$i = 0;
$columns = array();
$values = array();
$params = array();
foreach($columns_values as $column => $value) {
$i++;
$param = array($i => $value);
array_push($params, $param);
array_push($columns, $column);
array_push($values, '?');
}
// turn arrays into comma separated list
$columns = implode(",", $columns);
$values = implode(",", $values);
$stmt = $dbh->prepare("INSERT INTO $table ($columns) VALUES ($values)");
foreach ($params as $param_stmt) {
// i think this is where the problem is
foreach ($param_stmt as $placeholder => $value) {
$stmt->bindParam($placeholder, $value);
}
}
$stmt->execute();
return $stmt;
} // end insert()
I wouldn't do it your way. After a few minutes, I came up with this:
/**
* Function to insert a list of values to the database.
*
* #param PDO $pdo
* #param string $table
* #param array $columns_values
*
* #throws \Exception
* #throws \PDOException
*/
function insert_to_db(PDO $pdo, $table, array $columns_values) {
//Some data validation.
if (empty($columns_values)) {
throw new \Exception("Insert at least one value.");
}
if (empty($table)) {
throw new \Exception("Table may not be empty.");
}
//Implode all of column names. Will become the columns part of the query.
$str_columns = implode(", ", array_keys($columns_values));
//Implode all column names after adding a : at the beginning.
//They will become the placeholders on the values part.
$prepared_column_names = array_map(function ($el) {
return ":$el";
}, array_keys($columns_values));
$prepared_str_columns = implode(", ", $prepared_column_names);
//The query itself. Will look like "INSERT INTO `$table` (col1, col2, col3) VALUES (:col1, :col2, :col3);"
$query = "INSERT INTO `$table` ($str_columns) VALUES ($prepared_str_columns);";
//Prepare the query
$stmt = $pdo->prepare($query);
//Iterate over the columns and values, and bind the value to the placeholder
foreach ($columns_values as $column => $value) {
$stmt->bindValue(":$column", $value);
}
//Execute the query
$stmt->execute();
}
Things I changed
I don't instantiate the PDO object inside of the function. The function needs one in order to work, so it should be one of the arguments!
I throw Exceptions in case of an error. It's a better way of handling errors.
I use named placeholders instead of unnamed ones (:name vs ?). Produces more readable, easier to follow queries, should you ever need to debug.
Added comments to code. Again, you understand what you wrote now, but will you 6 months from now?
I made use of array_keys() to automatically generate an array full of keys (i.e. the columns), instead of looping and manually adding one.
Some tips
When you instantiate a PDO object, make sure it throws PDOExceptions on error! Like so:
new PDO($dsn, $user, $pass, array(PDO::PARAM_ERRMODE => PDO::ERRMODE_EXCEPTION));
or
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::PARAM_ERRMODE, PDO::ERRMODE_EXCEPTION);
That way, you don't need to explicitly check for errors each time, you use a single try catch block for the whole thing, and you're good:
try {
insert_to_db($pdo, $table, $array_of_columns_and_values);
}
catch (\Exception $e) { //Will catch all kinds of exceptions, including PDOExceptions
echo $e->getMessage();
}
You haven't checked that your prepare() actually succeeded:
$sql = "INSERT ....";
$stmt = $dbh->prepare($sql);
if (!$stmt) {
die($sql . $dbh->errorInfo());
}
Never assume a query succeeded, especially when you're building one totally dynamically as you are.
Without seeing what your original $columns_values array looks like.
Hope it helps
<?php
function insert($table, $values){
$dbh = $this->db_connect();
$fieldnames = array_keys($values[0]);
$sql = "INSERT INTO $table";
/*** set the field names ***/
$fields = '( ' . implode(' ,', $fieldnames) . ' )';
/*** set the placeholders ***/
$bound = '(:' . implode(', :', $fieldnames) . ' )';
/*** put the query together ***/
$sql .= $fields.' VALUES '.$bound;
//INSERT INTO testtable( id ,col1 ,col2 ) VALUES (:id, :col1, :col2 )
/*** prepare and execute ***/
$query = $dbh->prepare($sql);
foreach($values as $vals){
$query->execute($vals);
/* Array
(
[id] =
[col1] = someval1
[col2] = Someval21
)*/
}
}
//Multi Insert
$insert = array(array('id'=>'','col1'=>'someval1','col2'=>'Someval21'),
array('id'=>'','col1'=>'someval2','col2'=>'Someval22'),
array('id'=>'','col1'=>'someval3','col2'=>'Someval23'),
array('id'=>'','col1'=>'someval4','col2'=>'Someval24')
);
insert('testtable',$insert);
?>
I am trying to use staticsanĀ“s answer in this question for prepared statements.
Lets take this example:
$stmt = $mysqli->prepare("INSERT INTO something (userid, time, title) VALUES (?, ?, ?)");
$stmt->bind_param('iis', $userid, time(), $title);
$stmt->execute();
In staticsanĀ“s answer imploding the array is adding all the values into the mysql statement so that in the end we can insert multiple data into the database with just one statement.
How would this be done in my example?
This is completely valid:
$stmt = $mysqli->prepare("INSERT INTO something (userid, time, title) VALUES (?, ?, ?)");
$stmt->bind_param('iis', $userid, time(), $title);
$stmt->execute();
$stmt->bind_param('iis', $userid, time(), $title);
$stmt->execute();
$stmt->bind_param('iis', $userid, time(), $title);
$stmt->execute();
$stmt->bind_param('iis', $userid, time(), $title);
$stmt->execute();
You can foreach over your array of values to insert and bind and execute each time. It wont be quite as fast as the bulk insert in the example you linked, but it will be more secure.
You can build prepared statement using code as mentioned here,
PDO Prepared Inserts multiple rows in single query
PHP logic will be sort of like,
/**
* Insert With Ignore duplicates in Mysql DB.
*/
public static function insertWithIgnore($em, $container, $tableName, $fields, $rows)
{
$query = "INSERT IGNORE INTO $tableName (`" . implode('`,`', $fields) . "`) VALUES ";
$placeHolr = array_fill(0, count($fields), "?");
$qPart = array_fill(0, count($rows), "(" . implode(',', $placeHolr) . ")");
$query .= implode(",", $qPart);
$pdo = self::getPDOFromEm($em, $container);
$stmt = $pdo->prepare($query);
$i = 1;
foreach ($rows as $row) {
$row['created_at'] = date("Y-m-d H:i:s");
foreach ($fields as $f) {
if (!isset($row[$f])) {
$row[$f] = null;
}
$stmt->bindValue($i++, $row[$f]);
}
}
$result = $stmt->execute();
if ($result == false) {
$str = print_r($stmt->errorInfo(), true);
throw new \Exception($str);
}
$stmt->closeCursor();
$pdo = null;
}
/**
* Replace old rows in Mysql DB.
*/
public static function replace($em, $container, $tableName, $fields, $rows, $extraFieldValues = null)
{
if ($extraFieldValues != null) {
$fields = array_unique(array_merge($fields, array_keys($extraFieldValues)));
}
$query = "REPLACE INTO $tableName (`" . implode('`,`', $fields) . "`) VALUES ";
$placeHolr = array_fill(0, count($fields), "?");
$qPart = array_fill(0, count($rows), "(" . implode(',', $placeHolr) . ")");
$query .= implode(",", $qPart);
$pdo = self::getPDOFromEm($em, $container);
$stmt = $pdo->prepare($query);
$i = 1;
foreach ($rows as $row) {
if ($extraFieldValues != null) {
$row = array_merge($row, $extraFieldValues);
}
foreach ($fields as $f) {
$stmt->bindValue($i++, $row[$f]);
}
}
$stmt->execute();
if (!$stmt) {
throw new \Exception("PDO::errorInfo():" . print_r($stmt->errorInfo(), true));
}
$stmt->closeCursor();
$pdo = null;
}