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