How to solve my sluggish code (using PHP - PDO ) - php

I am using PHP PDO,
this the code :
for ($i = 0; $i < 1000; $i++) {
//select room and insert to chart
$thisDay=date ("Y-m-d");
$query = $db->prepare("SELECT room FROM d_room WHERE activ=1 ORDER BY room");
$query->execute();
for($a = 1; $result = $query->fetch(); $a++) {
$query2 = $db->prepare("INSERT INTO d_chart (date,room,status) VALUES (? + INTERVAL ? DAY,?,0)");
$query2->execute(array($thisDay,$i,$result['room']));
}
}
this code run too slow, How to make better code and fast, < 2 second.

I don't know why you are trying to do this. However,I suggest you not to store this fuzzy data in your database because there are other ways for doing such tasks if time and space constraint is the issue. As you said <2 seconds then storing that big result will not take less than 30seconds even on the local machine.
So what you can do is just store single result in database as follows:
$thisDay=date ("Y-m-d");
$query = $db->prepare("SELECT room FROM d_room WHERE activ=1 ORDER BY room");
$query->execute();
$result = $query->fetchAll(PDO::FETCH_ASSOC); // storing result here would reduce some time to fetch the data inside loop because your script would not require to get the data again and again from server.
$query = $db->prepare("INSERT INTO d_chart (date,room,status) VALUES (? + INTERVAL ? DAY,?,0)");
for($i=0; $i<sizeof($result);$i++)
{
$query->execute(array($thisDay,$i,$result[$i]['room']));
}
and if you want to retrieve the data for next 1000 days then just fetch the single day data from the database and show the result using some mathematical calculations for next 1000 days at the client side, which would be much faster than anything.
However, you should explain what is your aim behind doing such huge task so that you can get better answers.

You should be able to make this significantly simpler by using an INSERT...SELECT query (assuming MySQL)
// Make sure you see any errors that might occur
ini_set('display_errors', 'On');
error_reporting(E_ALL);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$thisDay = date('Y-m-d');
$stmt = $db->prepare('INSERT INTO d_chart (date, room, status)
SELECT (:thisDay + INTERVAL :days DAY), room, 0
FROM d_room WHERE activ = 1'); // there's no need to order these results
$stmt->bindParam(':thisDay', $thisDay);
$stmt->bindParam(':days', $i, PDO::PARAM_INT);
for ($i = 0; $i < 1000; $i++) {
$stmt->execute();
}
If d_room.activ has an index, this would be even faster.

Don't run the exact same query 1000 times!
Check your indexing to speed up the query.
Prepare the query, then execute it in the loop. This is what preparing is for.
Use PDO::beginTransaction and PDO::commit before and after the loop, respectively. Index updates can be expensive and it's faster to do them all at once.
That should at least make it as fast as possible.
$thisDay = date ("Y-m-d"); // Take this outside the loop
$query = $db->prepare("SELECT room FROM d_room WHERE activ=1 ORDER BY room");
$query2 = $db->prepare("INSERT INTO d_chart (date,room,status) VALUES (? + INTERVAL ? DAY,?,0)"); // Prepare this once
$query->execute(); // Just run this once
$rowset = $query->fetchAll (); // Put into an array to re-loop without querying again
$db->beginTransaction (); // Will update indices in bulk - faster
for ($i = 0; $i < 1000; $i++)
{
//select room and insert to chart
foreach ($rowset as $result)
$query2->execute(array($thisDay,$i,$result['room']));
}
$db->commit (); // Apply changes
As you can see, the loop is now much, much tighter - always the first place to look to speed up code.

I am not seeing any reason to run this query thousands time inside the loop. Because it is executing the same query. Put the query outside the loop.
<?php
//select room and insert to chart
$thisDay = date("Y-m-d");
$query = $db->prepare("SELECT room FROM d_room WHERE activ=1 ORDER BY room");
$query->execute();
$query2 = $db->prepare("INSERT INTO d_chart (date,room,status) VALUES (? + INTERVAL ? DAY,?,0)");
$result = $query->fetch();
for ($i = 0; $i < 1000; $i++) {
$query2->execute(array($thisDay, $i, $result['room']));
}

Related

How do I structure an array in this desired format that is run inside a foreach() loop?

In the following scenario, $communityPlayerIds is an array of the Id's of people in a community, and $noPlayers is the count of that array.
e.g
$communityPlayerIds = [2,5,6]
$noPlayers = 3
The following function should do the following:
Run an sql query for the number of times represented by $noPlayers, each time retrieving the desired data of a different $communityPlayerId.
At the moment this is creating one new array, players of 24 items, 8 for each player.
public function getCommunityForm($communityId, $noPlayers, $communityPlayersIds){
$returnValue = array();
$i = 0;
foreach ($communityPlayersIds as $cPI){
$sql = " SELECT player1_result, player1_name, date , results_id FROM `results` WHERE player1_id = '".$cPI."' AND community_id = '".$communityId."' UNION ALL SELECT player2_result, player2_name,date, results_id FROM `results` WHERE player2_id = '".$cPI."' AND community_id = '".$communityId."' ORDER BY date DESC Limit 8";
$result = $this->conn->query($sql);
if (mysqli_num_rows($result) === 0) {
$returnValue[] = ['status' => "nil"];
}
if($result != null && (mysqli_num_rows($result) >= 1)){
while($row = $result -> fetch_array(MYSQLI_ASSOC)){
if(!empty($row)){
$returnValue['players'][$i] = $row;
$i++;
}
}
}
}
return $returnValue;
}
What I want is to return a single array, that has within it 3 separate arrays, 1 for each query run.
How do I do this?
Use two separate counters. Use the $i counter for the queries, and another counter for the rows of each query.
In our code, move the increment of $i to the end of the foreach loop, so it gets incremented only one time each pass through that outer loop.
$i = 0
foreach ($communityPlayersIds as $cPI){
$sql = "...";
// process each query
$i++;
}
Within the body of the foreach loop, when you process the rows returned by a query, use another counter for the rows. Initialize before the loop, and increment as the last step in the loop.
And add another dimension to your result array
$rn = 0;
while($row = $result->fetch_array(MYSQLI_ASSOC)){
//
$returnValue['players'][$i][$rn] = ... ;
rn++;
}
EDIT
As Paul Spiegel notes, the $rn counter isn't strictly necessary. An assignment to an array using empty square brackets will add a new element to an array.
while($row = $result->fetch_array(MYSQLI_ASSOC)){
//
$returnValue['players'][$i][] = ... ;
}
First thing I did was simplify your database query (well, I at least made it more efficient) by getting rid of the UNION with the second query.
Next, I set up a prepared statement. This reduces all the overhead of repeated queries to the database. It's been a few years since I worked with mysqli but I believe it should be working, as long as your ID columns are all numbers. I would hope so, but your original code had quotes around them. If they're strings, change iiiii to sssss, and seriously reconsider your database schema (more on that below.)
You don't need a counter since you already have the player ID, just use that as the array index.
public function getCommunityForm($communityId, $noPlayers, $communityPlayersIds){
$sql = " SELECT IF(player1_id=?, player1_result, player2_result) AS result, IF(player1_id=?, player1_name, player2_name) AS name, date, results_id FROM `results` WHERE (player1_id=? OR player2_id=?) AND community_id=? ORDER BY date DESC Limit 8";
$stmt = $this->conn->prepare($sql);
foreach ($communityPlayersIds as $cPI) {
$stmt->bind_param("iiiii", $cPI, $cPI, $cPI, $cPI, $communityId);
$stmt->execute();
if ($result = $stmt->get_result()) {
while($row = $result->fetch_array(MYSQLI_ASSOC)){
$returnValue['players'][$cPI][] = $row;
}
}
}
}
return $returnValue;
}
And for free, here's the PDO version. I'd strongly recommending looking into PDO. It's more modern and less verbose than mysqli. You'll notice no binding of parameters, we get to used named parameters, and getting an array out of it is much easier.
public function getCommunityForm($communityId, $noPlayers, $communityPlayersIds){
$sql = " SELECT IF(player1_id=:pid, player1_result, player2_result) AS result, IF(player1_id=:pid, player1_name, player2_name) AS name, date, results_id FROM `results` WHERE (player1_id=:pid OR player2_id=:pid) AND community_id=:cid ORDER BY date DESC Limit 8";
$stmt = $this->conn->prepare($sql);
foreach ($communityPlayersIds as $cPI) {
if ($stmt->execute([":pid"=>$cPI, ":cid"=>$communityID])) {
$returnValue['players'][$cPI] = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
return $returnValue;
}
As for your database schema, you should not have a column for player names in your result table. How many times are names repeated in that table? What if a user wanted to change their name? You should instead have a player table, and then use a join to pull in their details.

How should I use for (i++) in select data mysql? Is that possible?

I am doing tag insert. So, I have several tags need to be insert into mysql. I explode it first, then I have a array[0],array[1],array[2]......., and I need to SELECT * FROM table WHERE key=array[0] and key=array[1] and key=array[2]......., instead of doing this way, I want to use i++, however, it doesn't work, I don't know why? Help please! appreciate!
<?php
$tag=strtolower($_POST['tag']);
$tag=explode(" ", $tag);
include 'db_tag.php';
for ($i = 0; $i <= 9; $i++){
$stmt = $db->prepare ("SELECT key FROM keyword WHERE key = :tag");
$tag_i=$tag[$i];
$stmt->bindParam(':tag', $tag_i);
}
$stmt->execute();
$row_tag = $stmt->fetchALL(PDO::FETCH_ASSOC);
foreach ($row_tag as $row_tag){
echo $row_tag['key'];
}
?>
For a SELECT :
First construct your SQL code:
$stmt = 'SELECT columns FROM keyword WHERE key = :tag0');
for ($i = 1; $i <= 9; $i++){
$stmt .= ' OR key = :tag' . $i;
}
Then fill up the params:
$stmt = $db->prepare($stmt);
for ($i = 0; $i <= 9; $i++){
$stmt->bindParam(':tag'+$i, $tag[$i]);
}
By the way, in your code you are trying to select a column (key) which is fixed in the WHERE predicates, it's probably not what you want, since you will get exactly the data you've put in the query parameters to begin with.
For an INSERT:
See this question.
I suggest IN STATEMENT (The IN operator allows you to specify multiple values in a WHERE clause.)
$in = join(',', array_fill(0, count($tag), '?'));
$stmt = $db->prepare ("SELECT key FROM keyword WHERE key IN ($in)");
$stmt->execute($tag);
The catch is to put as many question marks as parameters so use my code if it is dynamic otherwise you can hardcode it.

SQL update using PHP for loop takes too long

I am trying to UPDATE my table using a PHP for loop but it will take too long to work. I really have 200,000 people in my table. When I open the php file, the browser literally hungs itself :) When I open the phpMyAdmin, I can see it works, yet very slowly.
Is there a way to do the exact same thing using SQL, directly in phpMyAdmin?
for ($a = 0; $a < 200000; $a++) {
$rand = mt_rand(10000000, 99999999);
// checks whether the same url exists or not
$sec = "SELECT person_url FROM person WHERE person_url = '$rand'";
$result = $sqli->query($sec);
$row = $result->fetch_assoc();
$finish = $row['person_url'];
if ($finish == false) {
$sql = $sqli->prepare("UPDATE person SET person_url = '$rand' WHERE person_id = '$a'");
$sql->execute();
}
}
You seem to be updating every row in your table with a random number in a particular range. You don't need to do any of the PHP processing at all.
You can simply update the table with
update person set person_url = (cast(rand()*89999999 as signed)+10000000);
You run the risk of getting duplicate entries in the person_url field which you will need to resolve by some means.
One means of doing this is to set the person_url column to unique and use the query with ignore and a different where clause:
update ignore person set person_url = (cast(rand()*89999999 as signed)+10000000)
where person_url is null;
This will probably do the job in one pass, but if it doesn't, run it repeatedly until it does. You'd be very unlucky to have to use more than two or three passes and it's still likely to be much faster than manually checking for uniqueness with PHP.
Prepare the statements outside the loop and use bound parameters.
$sql1 = $sqli->prepare("SELECT person_url FROM person WHERE person_url = ?");
$sql1->bind_param('s', $rand);
$sql2 = $sqli->prepare("UPDATE person SET person_url = ? WHERE person_id = ?");
$sql2->bind_param('si', $rand, $a);
for ($i = 0; $a < 200000; $a++) {
$rand = mt_rand(10000000, 99999999);
// checks whether the same url exists or not
$sql1->execute();
$sql1->store_result();
if($sql1->num_rows == 0) {
$sql2->execute();
}
}

Issues when using multiple mysqli prepared statements

I am trying to insert and update data using 3 prepared statements. There are two problems happening:
1: The SELECT COUNT query checks for if the record exists with a LIMIT 1 and result stored in $check. However, the LIMIT 1 seems to be ignored such as when I have 3 rows matching the WHERE conditions in the SELECT COUNT, $check returns 3 instead of 1.
2: The if ( (int)$check < 1 ) statement works and whenever $check = 0, I see the output from my echo's, however the $stmt_insert->execute(); and $stmt_total->execute(); are not actually inserting and updating the database.
But, when I comment out:
$stmt_check->execute();
$stmt_check->bind_result($check);
$stmt_check->fetch();
then the $stmt_insert and $stmt_total works and the rows are inserted and updated. I'm wondering if there's a way to make all three statements work properly, for it seems some kind of conflict is occurring with my current set up. Thank you for your time.
Here is the full code:
$user_id = 100;
$count = preg_match_all("#<li>(.*?)</li>#is", $html, $matches, PREG_SET_ORDER);
$stmt_check = $mysqli->stmt_init();
$stmt_check->prepare("SELECT COUNT(`user_id`) AS `check` FROM `ua` WHERE `ach_class` = ? AND `user_id` = ? LIMIT 1");
$stmt_check->bind_param('si', $ach_class, $user_id);
$stmt_insert = $mysqli->stmt_init();
$stmt_insert->prepare("INSERT INTO `ua` (`user_id`, `ach_class`, `time_log`) VALUES (?, ?, ?)");
$stmt_insert->bind_param('isi', $user_id, $ach_class, $ach_timestamp);
$stmt_total = $mysqli->stmt_init();
$stmt_total->prepare("UPDATE `ach` SET `total` = `total` +1 WHERE `ach_class` = ? LIMIT 1");
$stmt_total->bind_param('s', $ach_class);
for ($i = 0; $i < $count; $i++) {
$li_block = $matches[$i][1];
preg_match('#img/(.*?)_60.png#is', $li_block, $class_matches);
$ach_class = $class_matches[1];
$ach_class = substr($ach_class, 11, -11);
$ach_timestamp = time();
$stmt_check->execute();
$stmt_check->bind_result($check);
$stmt_check->fetch();
echo $ach_class.' --- Check = '.$check.'<br />';
if ( (int)$check < 1 ) {
$stmt_insert->execute();
echo 'insert<br />';
$stmt_total->execute();
echo 'update<br />';
} else {
echo 'already present<br />';
}
}
$stmt_insert->close();
$stmt_check->close();
$stmt_total->close();
LIMIT works...it limits number of return rows, and your returning 1 row with just one field (just count), so that LIMIT at the end is kind of meaningless, because it will return single result, anyway. Also, why do you have LIMIT 1 in your UPDATE command?
You can use INSERT ON DUPLICATE KEY UPDATE and create UNIQUE index on your columns: ach_class, user_id and you will be fine.

Is there a more efficient way to do this

function countBrand($brand_id, $brand_name) {
$sql = "SELECT brand FROM coupons WHERE expire >= CURRENT_DATE AND brand='".$brand_id."'";
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
echo '<li>'.$brand_name.'</li>';
}
function brandCount() {
$sql = "SELECT DISTINCT brand,brand_id,brand_name FROM coupons,coupons_brand WHERE brand=brand_id AND expire >= CURRENT_DATE ORDER BY brand_name";
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
$num = mysql_num_rows($result);
echo '<h3>'.$num.' Brands</h3>';
echo '<ul>';
$i = 0;
while ($i < $num) {
$brand_id = mysql_result($result, $i, "brand_id");
$brand_name = mysql_result($result, $i, "brand_name");
countBrand($brand_id, $brand_name);
$i++;
}
echo '</ul>';
}
It works perfectly and gives me the results I am looking for. I am not as strong with sql statements as I would like to be. Is there a way I could do this that would be more efficient, it seems very slow.
Basically, it counts how many brands have coupons, then coupons how many coupons each brand has..
I also, on the same page, do this for categories. There are a few thousand categories and maybe 20,000 coupons.
there are a few php optimisations you could do, but they probably wont save you much time, compared to adding an index to mysql on the correct columns
i have commented some php optimisations below, maybe of interest to you anyway
given the simple nature of the functions, it is not necessary to have 2 functions, and that would save the timecost of calling countBrand(), (although it is a pretty minimal time saving)
function countBrand($brand_id, $brand_name) {
$sql = "SELECT brand FROM coupons WHERE expire >= CURRENT_DATE AND brand='".$brand_id."'";
$result = mysql_query($sql) || die (mysql_error()); // always check for errors
list($brand) = mysql_fetch_row($result);
// fetch row, returns a more concise array then mysql_fetch_array
// $row = mysql_fetch_array($result);
// use commas rather then . when concatenating echo statements
// dots force concatenation before output
echo '<li>',$brand,'</li>';
}
function brandCount() {
$sql = "SELECT DISTINCT brand,brand_id,brand_name FROM coupons,coupons_brand WHERE brand=brand_id AND expire >= CURRENT_DATE ORDER BY brand_name";
$result = mysql_query($sql) || die(mysql_error()); // always check for errors
// $row = mysql_fetch_array($result); // not sure why this is needed
$num = mysql_num_rows($result);
// use commas rather then . when concatenating echo statements
// dots force concatenation before output
echo '<h3>',$num,' Brands</h3>';
echo '<ul>';
// fetch all cols at once, rather then using lots of separate calls to mysql_result
// use mysql_fetch_row as it returns just the ordered values (vs mysql_fetch_assoc, and mysql_fetch_array)
//
while(list($brand, $brand_id, $brand_name) == mysql_fetch_row($result)) {
countBrand($brand_id, $brand_name);
}
// replaced with while loop above
// $i = 0;
// while ($i < $num) {
// $brand_id = mysql_result($result, $i, "brand_id");
// $brand_name = mysql_result($result, $i, "brand_name");
// countBrand($brand_id, $brand_name);
// $i++;
}
echo '</ul>';
}
those enhancements will only give you a minor speed increase
the biggest speed increase you will get is if you call the database less.
currently you select each brand, and then go back and count each brand individually
without knowing the structure of you tables this sql is difficult for me to write, so it is a guess, but it should point you in the right direction
SELECT brand, brand_id, brand_name, COUNT(*) as brandcount
FROM coupons
JOIN coupons_brand ON brand=brand_id
WHERE expire >= CURRENT_DATE
GROUP BY brand, brand_id, brand_name
ORDER BY brand_name
mysql_fetch_array($result,MYSQL_ASSOC);
SELECT SQL_CACHE brand FROM
Profiling query

Categories