How to perform multiple MySQL inserts in PHP - php

I need to insert many rows ( between 150 to 300 ) into MySQL table and I want to know the better of the following approaches in terms of performance:
Approach 1 :
foreach( $persons as $person ){
$stmt = $dbLink->prepare( "INSERT INTO table SET id = :ID,
name = :name,
email = :email,
mobile = :mobile");
$stmt->execute( array( ':ID'=>$person->getID(),
':name'=>$person->getName(),
':email'=>$person->getEmail(),
':mobile'=>$person->getMobile(),
) );
}
Approach 2:
$stmt = $dbLink->prepare( "INSERT INTO table SET id = :ID,
name = :name,
email = :email,
mobile = :mobile");
$stmt->bindParam( ':ID', $person->getID(), PDO::PARAM_STR );
$stmt->bindParam( ':name', $person->getName(), PDO::PARAM_STR );
$stmt->bindParam( ':email', $person->getEmail(), PDO::PARAM_STR );
$stmt->bindParam( ':mobile', $person->getMobile(), PDO::PARAM_STR );
foreach( $persons as $person ){
$stmt->execute();
}

It is the amount of calls to the database what makes the difference. Reduce the amount of calls as much as possible.
Instead of this:
insert (a,b,c) values (d,e,f);
insert (a,b,c) values (g,h,i);
insert (a,b,c) values (j,k,l);
insert (a,b,c) values (m,n,o);
do this:
insert (a,b,c) values (d,e,f),(g,h,i),(j,k,l),(m,n,o);
Thus making in one call what you would do in 4 calls.

You can use the below code to avoid multiple SQL calls and insert the data in Single SQL call
$first_string = "INSERT INTO table (id, name, email,mobile) VALUES ";//Basic query
foreach( $persons as $person )
{
$first_string .="(".$person->getID().",".$person->getName().",".$person->getEmail().",".$person->getMobile()."),";//Prepare the values
}
$final_query_string = substr($first_string, 0,-1);// This will remove the extra , at the end of the string
$stmt = $dbLink->prepare($final_query_string);
$stmt->execute();
Now execute the final query string prepared.
This way the query is prepared as the string and you need to execute it in one go.
This will make a single SQL call

To answer to your question, this is the way you should structure your prepare / bind / execute phases:
//prepare the query only the first time
$stmt = $dbLink->prepare( "INSERT table (id, name, email, mobile)
VALUES (:ID, :name, :email, :mobile)" );
//bind params and execute for every person
foreach( $persons as $person ){
$stmt->bindValue( ':ID', $person->getID(), PDO::PARAM_STR );
$stmt->bindValue( ':name', $person->getName(), PDO::PARAM_STR );
$stmt->bindValue( ':email', $person->getEmail(), PDO::PARAM_STR );
$stmt->bindValue( ':mobile', $person->getMobile(), PDO::PARAM_STR );
$stmt->execute();
}
If you have PDO::ATTR_EMULATE_PREPARES = false, the query will be prepared by mysql only the first time.
In the first case it would be re-prepared for every loop cycle
As correctly other users are saying, remember that a better performance improvement would be to make ONLY one insert instead of many insert in a for loop
EDIT: How to use parameter bindings AND one query
To use parameters' binding and only one query a solution could be:
$placeholders = ""; //this will be filled with placeholders : ( :id_1, :name_1, :email_1, :mobile_1),( :id_2 ... )
$parameters = array(); //this will keep the parameters bindings
$i = 1;
foreach( $persons as $person )
{
//add comma if not first iteration
if ( $placeholders )
$placeholders .= ", ";
//build the placeholders string for this person
$placeholders .= "( :id_$i, :name_$i, :email_$i, :mobile_$i )";
//add parameters for this person
$parameters[":id_$i"] = $person->getID();
$parameters[":name_$i"] = $person->getName();
$parameters[":email_$i"] = $person->getEmail();
$parameters[":mobile_$i"] = $person->getMobile();
$i++;
}
//build the query
$stmt = $dbLink->prepare( "INSERT INTO table (id, name, email, mobile)
VALUES " . $placeholders );
//execute the query passing parameters
$stmt->execute( $parameters );
In the first part of the loop we build the string $placeholders with a set of placeholders for every person, in the second part of the loop we store the bindings of the values of the placeholders in the $parameters array
At the end of the loop we should have all the placeholders and parameters set, and we can execute the query passing the $parameters array to the execute method. This is an alternative way in respect to use the bindValue / bindParam methods but the result should be the same
I think this is the only way to use parameter bindings AND use only one query

//declare array of values to be passed into PDO::Statemenet::execute()
$values = array();
//prepare sql string
$sql = 'INSERT INTO students ( id, name, email, mobile ) VALUES ';
foreach( $students as $student ){
$sql .= '( ?, ?, ?, ? ),'; //concatenate placeholders with sql string
//generate array of values and merge with previous values
$values = array_merge( $values, array( $student->getID(),
$student->getName(),
$student->getEmail(),
$student->getMobile(),
)
);
}
$sql = rtrim( $sql, ',' ); //remove the trailing comma (,) and replace the sql string
$stmt = $this->dbLink->prepare( $sql );
$stmt->execute( $values );
Full credits to all who have inspired me to arrive at this solution. This seems to be terse and clear:
In particular, the answer of JM4 at PDO Prepared Inserts multiple rows in single query really helped. I also recognise Moppo on this page.

Related

PHP - using a variable as part of a column name in an INSERT INTO MySQL statement

I have a database set up and there are 2 different columns and I want to insert values into one of those two columns dynamically based on an ID that is passed in from $_GET. I have the bindParam variable part working, but I'm not sure how to use a variable in the INSERT INTO portion of the statement.
One column is called product1_vote and the other is product2_vote. I am getting the 1 or 2 from $_GET and I want to pass that into the prepare call to determine which column to update.
$productID = $_GET['id'];
$stmt = $pdo->prepare('INSERT INTO products (id, title, product1_vote)
VALUES(:id, :title, :product1_vote);
$id = $pdo->lastInsertId();
$title = 'Test';
$date = date('m/d/Y h:i:s', time());
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->bindParam(':title', $title, PDO::PARAM_STR);
$stmt->bindParam(':product1_vote', $date, PDO::PARAM_STR);
How would I go about changing the INSERT INTO part to work dynamically instead of the current hardcoded product1_vote.
Something like this to give you an idea of what I'm after:
$stmt = $pdo->prepare('INSERT INTO products (id, title, product.$productID._vote)
VALUES(:id, :title, :product.$productID._vote);
$id = $pdo->lastInsertId();
$title = 'Test';
$date = date('m/d/Y h:i:s', time());
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->bindParam(':title', $title, PDO::PARAM_STR);
$stmt->bindParam(':product.$productID._vote', $date, PDO::PARAM_STR);
You can't parameterise a column name, but also, to guard against SQL injection you don't want to allow direct user input into the query without validation.
A common solution to this is to make a "whitelist" of allowed values and ensure that the user-provided value matches one of them before including it in the query.
For example:
$productID = $_GET['id'];
$voteIDs = ["1", "2"];
if (!in_array($productID, $voteIDs)) {
echo "invalid input value";
die();
};
$stmt = $pdo->prepare('INSERT INTO products (id, title, product'.$productID.'_vote)
VALUES(:id, :title, :product1_vote);
P.S. It's possible this has arisen because your database could be better normalised. If you have multiple votes per product, consider storing them in a separate "productVotes" table with a foreign key back to the products table. Then you wouldn't need to vary the column names in your query.

How to bind multiple params to prepare statement [duplicate]

Is there's an easy way of binding multiple values in PDO without repitition ? Take a look at the following code :
$result_set = $pdo->prepare("INSERT INTO `users` (`username`, `password`, `first_name`, `last_name`) VALUES (:username, :password, :first_name, :last_name)");
$result_set->bindValue(':username', '~user');
$result_set->bindValue(':password', '~pass');
$result_set->bindValue(':first_name', '~John');
$result_set->bindValue(':last_name', '~Doe');
$result_set->execute();
Here, I binded values in a repepeated way which is 4 times. So is there's an easy way of binding multiple values in PDO ?
You can always bind values within the arguments of execute() as long as you're fine with the values being treated as PDO::PARAM_STR (string).
$result_set = $pdo->prepare("INSERT INTO `users` (`username`, `password`, `first_name`, `last_name`) VALUES (:username, :password, :first_name, :last_name)");
$result_set->execute(array(
':username' => '~user',
':password' => '~pass',
':first_name' => '~John',
':last_name' => '~Doe'
));
You can use the array passed just like any array:
$user = "Nile";
$pdo->execute(array(":user" => $user));
If you want to bind based on type (string, int, etc), then no. If you're fine with binding everything as a string:
$stmt = $db->prepare("...");
$stmt->execute(array(
'foo' => 'bar',
'something' => 'else',
'third' => 'thing',
));
To truly never type anything twice, you can use an array to supply the data, and use a function on that same array to output the binding portion of the MySQL query. For example:
function bindFields($fields){
end($fields); $lastField = key($fields);
$bindString = ' ';
foreach($fields as $field => $data){
$bindString .= $field . '=:' . $field;
$bindString .= ($field === $lastField ? ' ' : ',');
}
return $bindString;
}
The data and column names come from a single associative array ($data). Then, use bindFields($data) to generate a string of column = :column pairs to concatenate into the MySQL query:
$data = array(
'a_column_name' => 'column data string',
'another_column_name' => 'another column data string'
);
$query = "INSERT INTO tablename SET" . bindFields($data);
$result = $PDO->prepare($query);
$result->execute($data);
bindFields($data) output:
a_column_name=:a_column_name,another_column_name=:another_column_name

insert multiple rows in database using pdo's

insert into test (sometext) values ("?"),("?")
$a= array("weird' text","sdfa");
I want to insert text into the table test in column sometext using bind parameter and I do not want the execute statement in a loop. I cannot implode the array in ("?"),("?") form as the query might crash coz the text can be composed of quotes.
So is there a way to achieve this using PDO in one(1) execute statement?
I cannot implode the array in ("?"),("?") form as the query might crash coz the text can be composed of quotes.
The prepared statements are there to solve quoting/escaping problems.
This syntax is wrong1:
insert into test (sometext) values ("?"),("?")
You don't have to wrap parameters by quotes, you have to write query in this form:
INSERT INTO test (sometext) VALUES (?),(?)
Then, you can use implode() without worrying about quotes:
$a = array( "weird' text", "sdfa" );
$query = "INSERT INTO test (sometext) VALUES (" . implode( "),(", array_fill( 0, count( $a ), "?" ) ) . ")";
$stmt = $db->prepare( $query );
$stmt->execute( $a );
As alternative, you can use substr and str_repeat instead of implode:
$query = "INSERT INTO test (sometext) VALUES " . substr( str_repeat( "(?),", count( $a ) ), 0, -1 );
1 Using insert into test (sometext) values ("?"),("?") you insert in your fields literally two question marks.
$stmt = $conn->prepare("INSERT INTO test (field1, field2, field3) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $field1, $field2, $field3);
// set parameters and execute
$field1 = "test";
$field2 = "test2";
$field3 = "test#test.cc";
$stmt->execute();

More efficient way of inserting data into mysql using PDo

Ok so I execute the following code for inserting data into database:
$db_conn->beginTransaction();
$query = $db_conn->prepare('INSERT INTO mytable(name, user) VALUES(:name, :user)');
foreach($UploadData AS $DataValue)
{
$query->execute(array(':name' => $DataValue['Name'],':user' =>$_SESSION['user']));
}
$db_conn->commit();
Now in this code block execute() runs 100s time if I have that much data. Like before I use to do with basic mysqli concatenation and executes the query only once.
Will that can be done here with PDO also?
$SQL = 'INSERT INTO mytable (name, user) VALUES';
foreach( $UploadData AS $DataValue)
$SQL .= sprintf(" ( '%s', '%s' ),", $DataValue['Name'], $DataValue['user'] );
$SQL = substr($SQL, -1);
$query = $db_conn->prepare($SQL);
$query->execute();
Result
INSERT INTO mytable (name, user) VALUES ('VAL', 'VAL'), ('VAL', 'VAL') ....

INSERT array - PDO

I've got a portion of code that is supposed to take the data entered in a form, store it in an array and then enter it into the database. I have used var_dump on $fields and $data and they are both returning the information entered in the field (in the add_habbo function). So the problem I've got is that the MYSQL/PDO code isn't inserting this data into the database.
This is the code that I am using to insert them into the database:
$fields = '`' . implode('`, `', array_keys($habbo_data)) . '`';
$data = '\'' . implode('\', \'', $habbo_data) . '\'';
var_dump($fields);
var_dump($data);
global $con;
$query = "INSERT INTO `personnel` (:fields) VALUES (:data)";
$result = $con->prepare($query);
$result->bindParam(':fields', $fields, PDO::PARAM_STR);
$result->bindParam(':data', $data, PDO::PARAM_STR);
$result->execute();
I get the impression it has something to with the bindParam sections, possibly PDO::PARAM_STR? Thanks for your assistance!
Update:
$fields = '`' . implode('`, `', array_keys($habbo_data)) . '`';
$fields_data = ':' . implode(', :', array_keys($habbo_data));
var_dump($fields);
var_dump($fields_data);
global $con;
$query = "INSERT INTO `personnel` (`rank`, `habbo_name`, `rating`, `asts`, `promotion_date`, `transfer_rank_received`, `cnl_trainings`, `rdc_grade`,
`medals`, `branch`) VALUES ({$fields_data})";
$result = $con->prepare($query);
$result->execute($habbo_data);
$arr = $result->errorInfo();
print_r($arr);
Error:
Array ( [0] => 21S01 [1] => 1136 [2] => Column count doesn't match
value count at row 1 )
Prepared statements are not the same as copy and paste!
INSERT INTO `personnel` (:fields) VALUES (:data)
You're telling PDO/MySQL here that you want to insert exactly one piece of data (:data) into one field (:field). The value is one string containing commas, not several values separated by commas.
Furthermore you can only bind data, not structural information like field names. You will have to create a query like so:
INSERT INTO `personnel` (foo, bar, baz) VALUES (?, ?, ?)
and then bind data to the three placeholders separately.
You cannot do that:
You need to add each variable / field-name and value individually;
You can only bind values and not table- or field-names.
Table- and field-names you will have to inject directly into your sql so to prevent sql injection problems, you need to check them against a white-list before doing that.
So in your case that would be something like (rough draft):
// assuming all fields have been checked against a whitelist
// also assuming that the array keys of `$habbo_data` do not contain funny stuff like spaces, etc.
$fields = '`' . implode('`, `', array_keys($habbo_data)) . '`';
$fields_data = ':' . implode(', :', array_keys($habbo_data));
var_dump($fields);
var_dump($fields_data);
global $con;
$query = "INSERT INTO `personnel` ({$fields}) VALUES ({$fields_data})";
$result = $con->prepare($query);
$result->execute($habbo_data);
Note that I am not manually binding the variables any more but sending the associative $habbo_data array directly as a parameter to the execute method, see example #2.

Categories