Multiple row inserts as fast as possible - php

I've seen multiple threads discussing this but there always has been totally different conclusion in the answers. Especially I wonder whether it is really necessary to create a own prepared statement (with the right amount of placeholders) in order to insert it as single query. I expected that when I use beginTransaction and endTransaction before and after my for loop, that pdo/php waits with the transaction until all data is collected and it will send these data's as a single query once the server hits the line endTransaction.
How would I need to rewrite such a for loop insert with multiple inserts in order to reach the best performance (it has between 1 and 300 rows usually but it also could reach 2000 rows).
for($i=0; $i<$baseCount; $i++)
{
$thLevel = $bases[$i]["ThLevel"];
$gold = $bases[$i]["Gold"];
$elixir = $bases[$i]["Elixir"];
$darkElixir = $bases[$i]["DarkElixir"];
$dateFound = $elixir = $bases[$i]["TimeFound"];
$query = $db->prepare("INSERT INTO bot_attacks_searchresults (attack_id, available_gold, available_elixir, available_dark_elixir, date_found, opponent_townhall_level)
VALUES (:attack_id, :available_gold, :available_elixir, :available_dark_elixir, :date_found, :opponent_townhall_level)");
$query->bindValue(':attack_id', $attackId);
$query->bindValue(':available_gold', $gold);
$query->bindValue(':available_elixir', $elixir);
$query->bindValue(':available_dark_elixir', $darkElixir);
$query->bindValue(':date_found', $dateFound);
$query->bindValue(':opponent_townhall_level', $thLevel);
$query->execute();
}

Prepare the statement once. MySQL lexes it once, so any subsequent call to the query will be quick since it's already lexed and juts needs parameters.
Start the transaction before the loop. This is done so your hard drive can write down all the rows in one input output operation. The default mode is that 1 insert query = 1 I/O of the hdd.
Create the loop, bind your parameters there and call the $query->execute();
Exit the loop and commit() the transaction.
Full code:
$db->beginTransaction();
$query = $db->prepare("INSERT INTO bot_attacks_searchresults (attack_id, available_gold, available_elixir, available_dark_elixir, date_found, opponent_townhall_level)
VALUES (:attack_id, :available_gold, :available_elixir, :available_dark_elixir, :date_found, :opponent_townhall_level)");
for($i = 0; $i < $baseCount; $i++)
{
$thLevel = $bases[$i]["ThLevel"];
$gold = $bases[$i]["Gold"];
$elixir = $bases[$i]["Elixir"];
$darkElixir = $bases[$i]["DarkElixir"];
$dateFound = $elixir = $bases[$i]["TimeFound"];
$query->bindValue(':attack_id', $attackId);
$query->bindValue(':available_gold', $gold);
$query->bindValue(':available_elixir', $elixir);
$query->bindValue(':available_dark_elixir', $darkElixir);
$query->bindValue(':date_found', $dateFound);
$query->bindValue(':opponent_townhall_level', $thLevel);
$query->execute();
}
$db->commit();

Here's a very crude proof of concept:
<?php
$values = array();
for($i=0;$i<10;$i++)
{
$values[] = "($i)";
}
$values = implode($values,',');
$query = "INSERT INTO my_table VALUES $values";
echo $query;
?>
outputs INSERT INTO my_table VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)
You would need to restructure this slightly to work with prepare (PHP is not my forte), but the principle is the same; i.e. you build the query inside the loop, but execute it only once.

Related

PHP PDO sqlsrv large result set inconsistency

I am using PDO to execute a query for which I am expecting ~500K results. This is my query:
SELECT Email FROM mytable WHERE flag = 1
When I run the query in Microsoft SQL Server management Studio I consistently get 544838 results. I wanted to write a small script in PHP that would fetch these results for me. My original implementation used fetchAll(), but this was exhausting the memory available to php, so I decided to fetch the results one at a time like so:
$q = <<<QUERY
SELECT Email FROM mytable WHERE flag = 1
QUERY;
$stmt = $conn->prepare($q);
$stmt->execute();
$c = 0;
while ($email = $stmt->fetch()[0]) {
echo $email." $c\n";
$c++;
}
but each time I run the query, I get a different number of results! Typical results are:
445664
445836
445979
The number of results seems to be short 100K +/- 200 ish. Any help would be greatly appreciated.
fetch() method fetches one row at a time from current result set. $stmt->fetch()[0] is the first column of the current row.
Your sql query has no ordering and can have some null or empty values (probably).
Since you are controlling this column value in while loop, if the current row's first value is null, it will exit from the loop.
Therefore, you should control only fetch(), not fetch()[0] or something like that.
Also, inside the while loop, use sqlsrv_get_field() to access the columns by index.
$c = 0;
while ($stmt->fetch()) { // You may want to control errors
$email = sqlsrv_get_field($stmt, 0); // get first column value
// $email can be false on errors
echo $email . " $c\n";
$c++;
}
sqlsrv_fetch

Table only populating with one value from array

I have an array from which I would like to populate table records from, unfortunately it will only populate the 1st record of the array. I anticipate I have my increments declared incorrectly, but cannot find a combination that will work. In addition I would like '$mtcelogID = $siteNAME.'.'.$Maindate.'.'.$i;' to have the last part of the ID to increment
$mtcelogARRAY = $objPHPExcel->setActiveSheetIndex(2)->rangeToArray('A8:A18');
$num_mtcelog = count($mtcelogARRAY); // Here get total count of row in that Excel sheet
for( $i=0; $i<=$num_mtcelog; $i++ ){
$sql_mtcelog = "INSERT INTO `maintenance_log`(`mtcelogID`,`mtcelogTYPE`,`MaintenanceID`) VALUES (?,?,?)";
$query_mtcelogARRAY = mysqli_prepare($link, $sql_mtcelog);
$mtcelogID = $siteNAME.'.'.$Maindate.'.'.$i;
mysqli_stmt_bind_param($query_mtcelogARRAY,"sss", $mtcelogID, $mtcelogARRAY[$i][0], $MaintenanceID);
mysqli_stmt_execute($query_mtcelogARRAY);
mysqli_stmt_close($query_mtcelogARRAY);
}
The above code returns this in my PHP table:
And my array looks like this:
Thanks in advance
I know you're using mysqli, but I'm going to leave this PDO answer here. If this code is a small maintenance script, there shouldn't be any trouble dumping mysqli. Notice no binding is necessary, you just pass the values as an array to PDOStatement::execute(). No worrying about how many s and i you have. Also, foreach is a much more flexible and less verbose construct than for.
$pdo = new PDO("mysql:host=localhost;dbname=mydatabase", $username, $password);
$mtcelogARRAY = $objPHPExcel->setActiveSheetIndex(2)->rangeToArray('A8:A18');
$sql_mtcelog = "INSERT INTO `maintenance_log`(`mtcelogID`,`mtcelogTYPE`,`MaintenanceID`) VALUES (?,?,?)";
$stmt = $pdo->prepare($sql_mtcelog);
foreach ($mtcelogARRAY as $i=>$arr) {
$params = ["$siteNAME.$Maindate.$i", $arr[0], $MaintenanceID];
$stmt->execute($params);
}
The important thing is to prepare your statement outside the loop. One of the main goals of prepared statements is to reduce overhead; by preparing the statement repeatedly you are increasing overhead.

PHP PDO proper way to bind values from array

I'm trying to get better at using PDO, I have this code:
$answers_count = count($answers);
$save_answers = $conn->prepare("INSERT INTO answers (answer, is_correct, question_id) VALUES (:answer, :is_correct, :question_id)");
for($i = 0; $i < $answers_count; $i++) {
$save_answers->bindParam(':answer', $answers[$i]);
$save_answers->bindParam(':is_correct', $answers_state[$i]);
$save_answers->bindParam(':question_id', $last_insert_id);
$save_answers->execute();
}
This code works for me well, but I have read that I should call execute() method just once, if I understood it correctly, I have to prepare sql statement once and execute it after I bind params? If I use execute() method for inserting one new record at a time it works, but if place $save_answers->execute(); statement outside of for loop only one INSERT query will be executed.
am I doing something wrong here, is there other easier way to bind values from the array where each time the number of array elements can be different.
Thank you in advance for the information you can provide me.
but if place $save_answers->execute(); statement outside of for loop only one INSERT query will be executed.
This is because if you place the execute statement outside of your loop it will only execute the query once for the values bound from the last iteration of the for loop. Therefore your current code is correct and rebinding and re-executing the query should be the way to go.
The query needs to bind the values from each iteration (each answer has different values and thus, each insertion has different insertion values). Obviously you need to re-bind the values from each answer, so doing it once will not cut it for you.
If you don't want to execute it via a for loop, you can try batch insertion:
PDO Prepared Inserts multiple rows in single query
This will allow you to do the insertion of multiple rows in one request to the database, which might be what you are looking for.
prepare query first and then execute
$answers_count = count($answers);
$writeArguments = array();
$writeQuery="insert into $tableName (answer, is_correct, question_id) values ";
for($i = 0; $i < $answers_count; $i++) {
if (i > 0) {
$writeQuery .= ',';
}
$writeQuery .= '(?,?,?)';
array_push($writeArguments, $answers[$i], $answers_state[$i], $last_insert_id);
}
$save_answers = $conn->prepare($writeQuery);
$save_answers->execute($writeArguments);

php request of mysql query timeout

i'm trying to make a long mysql query and process and update the row founded:
$query = 'SELECT tvshows.id_show, tvshows.actors FROM tvshows where tvshows.actors is not NULL';
$result = mysql_query($query);
$total = mysql_num_rows($result);
echo $total;
while ($db_row = mysql_fetch_assoc($result))
{
//process row
}
but after 60 second give me a timeout request, i have try to insert these in my php code:
set_time_limit(400);
but it's the same, how i can do?
EDIT:
only the query:
$query = 'SELECT tvshows.id_show, tvshows.actors FROM tvshows where tvshows.actors is not NULL';
takes 2-3 second to perform, so i think the problem is when in php i iterate all the result to insert to row or update it, so i think the problem is in the php, how i can change the timeout?
EDIT:
here is the complete code, i don't think is a problem here in the code...
$query = 'SELECT tvshows.id_show, tvshows.actors FROM tvshows where tvshows.actors is not NULL';
$result = mysql_query($query);
$total = mysql_num_rows($result);
echo $total;
while ($db_row = mysql_fetch_assoc($result)) {
//print $db_row['id_show']."-".$db_row['actors']."<BR>";
$explode = explode("|", $db_row['actors']);
foreach ($explode as $value) {
if ($value != "") {
$checkactor = mysql_query(sprintf("SELECT id_actor,name FROM actors WHERE name = '%s'",mysql_real_escape_string($value))) or die(mysql_error());
if (mysql_num_rows($checkactor) != 0) {
$actorrow = mysql_fetch_row($checkactor);
$checkrole = mysql_query(sprintf("SELECT id_show,id_actor FROM actor_role WHERE id_show = %d AND id_actor = %d",$db_row['id_show'],$actorrow[0])) or die(mysql_error());
if (mysql_num_rows($checkrole) == 0) {
$insertactorrole = mysql_query(sprintf("INSERT INTO actor_role (id_show, id_actor) VALUES (%d, %d)",$db_row['id_show'],$actorrow[0])) or die(mysql_error());
}
} else {
$insertactor = mysql_query(sprintf("INSERT INTO actors (name) VALUES ('%s')",mysql_real_escape_string($value))) or die(mysql_error());
$insertactorrole = mysql_query(sprintf("INSERT INTO actor_role (id_show, id_actor, role) VALUES (%d, %d,'')",$db_row['id_show'],mysql_insert_id())) or die(mysql_error());
}
}
}
}
Should definitely try what #rid suggested, and to execute the query on the server and see the results/duration to debug - if the query is not a simple one, construct it as you would in your PHP script, and only echo the SQL command, don't have to execute it, and just copy that in to the server MySQL command line or whichever tool you use.
If you have shell access, use the top command after running the above script again, and see if the MySQL demon server is spiking in resources to see if it really is the cause.
Can you also try a simpler query in place of the longer one? Like just a simple SELECT count(*) FROM tvshows and see if that also takes a long time to return a value?
Hope these suggestions help.
There are so many problems with your code.
Don't store multiple values in a single column. Your actors column is pipe-delimited text. This is a big no-no.
Use JOINs instead of additional queries. You can (or could, if the above weren't true) get all of this data in a single query.
All of your code can be done in a single query on the server. As I see it, it takes no input from the user and produces no output. It just updates a table. Why do this in PHP? Learn about INSERT...SELECT....
Here are some resources to get you started (from Googling, but hopefully they'll be good enough):
http://www.sitepoint.com/understanding-sql-joins-mysql-database/
http://dev.mysql.com/doc/refman/5.1/en/join.html
http://dev.mysql.com/doc/refman/5.1/en/insert-select.html
What is Normalisation (or Normalization)?
Let me know if you have any further questions.

Mysql insert using array

I have an array stored in a variable $contactid. I need to run this query to insert a row for each contact_id in the array. What is the best way to do this? Here is the query I need to run...
$contactid=$_POST['contact_id'];
$eventid=$_POST['event_id'];
$groupid=$_POST['group_id'];
mysql_query($query);
$query="INSERT INTO attendance (event_id,contact_id,group_id) VALUES ('$eventid','$contactid','$groupid')";
Use a foreach loop.
$query = "INSERT INTO attendance (event_id,contact_id,group_id) VALUES ";
foreach($contactid as $value)
{
$query .= "('{$eventid}','{$value}','{$groupid}'),";
}
mysql_query(substr($query, 0, -1));
The idea here is to concatenate your query string and only make 1 query to the database, each value-set is separated by a comma
Since no one hasn't stated that yet, you actually cannot do this:
$query = '
INSERT INTO [Table] ([Column List])
VALUES ([Value List 1]);
INSERT INTO [Table] ([Column List])
VALUES ([Value List 2]);
';
mysql_query($query);
as this has been prevented to prevent sql injections in the mysql_query code. You cannot have semicolon within the given query param with mysql_query. With the following exception, taken from the manual comments:
The documentation claims that "multiple queries are not supported".
However, multiple queries seem to be supported. You just have to pass
flag 65536 as mysql_connect's 5 parameter (client_flags). This value
is defined in /usr/include/mysql/mysql_com.h:
#define CLIENT_MULTI_STATEMENTS (1UL << 16) /* Enable/disable multi-stmt support */
Executed with multiple queries at once, the mysql_query function will
return a result only for the first query. The other queries will be
executed as well, but you won't have a result for them.
That is undocumented and unsupported behaviour, however, and easily opens your code to SQL injections. What you can do with mysql_query, instead, is
$query = '
INSERT INTO [Table] ([Column List])
VALUES ([Value List 1])
, ([Value List 2])
[...]
, ([Value List N])
';
mysql_query($query);
so you can actually insert multiple rows with a one query, and with one insert statement. In this answer there's a code example for it which doesn't concatenate to a string in a loop, which is better than what's suggested in this thread.
However, disregarding all the above, you're probably better of still to use a prepared statement, like
$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();
Use something like the following. Please note that you shouldn't be using mysql_* functions anymore, and that your code is suseptible to injection.
for ($i = 0; $i < count($contactid); $i++) {
$query="INSERT INTO attendance (event_id,contact_id,group_id) VALUES ('$eventid','$contactid[$i]','$groupid')";
mysql_query($query);
}
I'm not sure running multiple queries is the best thing to do, so won't recommend making a for loop for example, that runs for each element of the array. I would rather say, make a recursive loop, that adds the new elements to a string, that then gets passed to the query. In case you can give us a short example of your DB structure and how you'd like it to look like (i.e. how the array should go into the table), I could give you an example loop syntax.
Cheers!
What about:
$contactIds = $_POST['contact_id'];
$eventIds = $_POST['event_id'];
$groupIds = $_POST['group_id'];
foreach($contactIds as $key => $value)
{
$currentContactId = $value;
$currentEventId = $eventIds[$key];
$currentGroupId = $groupIds[$key];
$query="INSERT INTO attendance (event_id,contact_id,group_id) VALUES ('$currentEventId','$currentContactId','$currentGroupId')";
mysql_query($query);
}
Well, you could refactor that to insert everything in a single query, but you got the idea.

Categories