PHP: mysql_fetch_array() in a while-loop takes too long - php

I am creating an online calendar for a client using PHP/MySQL.
I initiated a <table> and <tr>, and after that have a while loop that creates a new <td> for each day, up to the max number of days in the month.
The line after the <td>, PHP searches a MySQL database for any events that occur on that day by comparing the value of $i (the counter) to the value of the formatted Unix timestamp within that row of the database. In order to increment the internal row counter ONLY when a match is made, I have made another while loop that fetches a new array for the result. It is significantly slowing down loading time.
Here's the code, shortened so you don't have to read the unnecessary stuff:
$qry = "SELECT * FROM events WHERE author=\"$author\"";
$result = mysql_query($qry) or die(mysql_error());
$row = mysql_fetch_array($result);
for ($i = 1; $i <= $max_days; $i++) {
echo "<td class=\"day\">";
$rowunixdate_number = date("j", $row['unixdate']);
if ($rowunixdate_number == $i) {
while ($rowunixdate_number == $i) {
$rowtitle = $row['title'];
echo $rowtitle;
$row = mysql_fetch_array($result);
$rowunixdate_number = date("j", $row['unixdate']);
}
}
echo "</td>";
if (newWeek($day_count)) {
echo "</tr><tr>";
}
$day_count++;
}

The slowness is most likely because you're doing 31 queries, instead of 1 query before you build the HTML table, as Nael El Shawwa pointed out -- if you're trying to get all the upcoming events for a given author for the month, you should select that in a single SQL query, and then iterate over the result set to actually generate the table. E.g.
$sql = "SELECT * FROM events WHERE author = '$author' ORDER BY xdate ASC";
$rsEvents = mysql_query($sql);
echo("<table><tr>");
while ($Event = mysql_fetch_array($rsEvents)) {
echo("<td>[event info in $Event goes here]</td>");
}
echo("</tr></table>");
Furthermore, it's usually a bad idea to intermix SQL queries and HTML generation. Your external data should be gathered in one place, the output data generated in another. My example cuts it close, by having the SQL immediately before the HTML generation, but that's still better than having an HTML block contain SQL queries right in the middle of it.

Have you run that query in a MySQL tool to see how long it takes?
Do you have an index on the author column?
There's nothing wrong with your PHP. I suspect the query is the problem and no index is the cause.

aside from their comments above, also try to optimize your sql query since this is one of the most common source of performance issues.
let say you have a news article table with Title, Date, Blurb, Content fields and you only need to fetch the title and display them as a list on the html page,
to do a "SELECT * FROM TABLE"
means that you are requiring the db server to fetch all the field data when doing the loop (including the Blurb and Content which you are not going to use).
if you optimize that to something like:
"SELECT Title, Date FROM TABLE" would fetch only the necessary data and would be more efficient in terms of server utilization.
i hope this helps you.

Is 'author' an id? or a string? Either way an index would help you.
The query is not slow, its the for loop thats causing the problem. Its not complete; missing the $i loop condition and increment. Or is this a typo?
Why don't you just order the query by the date?
SELECT * FROM events WHERE author=? ORDER BY unixdate ASC
and have a variable to store the current date you are on to have any logic required to group events by date in your table ex. giving all event rows with the same date the same color.
Assuming the date is a unix timestamp that does not account for the event's time then you can do this:
$currentDate = 0;
while(mysql_fetch_array($result)){
if($currentDate == $row['unixdate']){
//code to present an event that is on the same day as the previous event
}else{
//code to present an even on a date that is past the previous event
//you are sorting events by date in the query
}
//update currentDate for next iteration
$currentDate = $row['unixdate'];
}
if unixdate includes the event time, then you need to add some logic to just extract the unix date timestmap excluding the hours and minutes.
Hope that helps

Related

Count unique values from the results of a prepared MySQL statement in PHP

I have an online calendar listing upcoming live music. I use a prepared statement to fetch the listings for the next eight days and display them in a table. What I need to do, before displaying the results, is count the number of unique dates ('Date') within the listings. For example, if only five days out of the next eight have events happening, I need to know that number is 5.
Using COUNT(DISTINCT) works for giving me that number, but then it only displays one row of results, so I need another solution
My code is this:
$mysqli = new mysqli("Login Stuff Here");
if ($mysqli->connect_errno){
echo "Failed to connect to MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
$start = strtotime('today midnight');
$stop = strtotime('+1 week');
$start = date('Y-m-d', $start);
$stop = date('Y-m-d', $stop);
$allDates = $mysqli->prepare("SELECT
ID,
Host,
Type,
Bands,
Date,
Time,
Price,
Note,
Zip,
URL
FROM things WHERE (Date >= ? AND Date <= ?) ORDER BY Date, Time, Host");
$allDates->bind_param("ss", $start, $stop);
$allDates->execute();
$allDates->bind_result($ID, $Host, $Type, $Bands, $Date, $Time, $Price, $Note, $Zip, $URL);
while($allDates->fetch()):
// ECHO ALL THE INFO IN A NICE TABLE
endwhile;
$allDates->close();
I need to count the unique values (and maybe even retrieve them) from the 'Date' column. Right now I have it working by doing a separate query, but I'm sure there's a better way.
EDIT: Ultimately, I wound up doing a separate query, which worked out well as I was able to use it for other things as well. I found that using GROUP BY always only returned just one result per date, so it didn't work for displaying the full listings. Maybe I was going at it wrong, but I wound up being good in another way. Thanks!
You are missing group by:
GROUP BY Date
To use any aggregate method like count, sum for specific group. You need to apply GROUP BY on specific column or list of columns.
Example :
GROUP BY Date ORDER BY Date, Time, Host
Please note that, list of columns in SELECT MUST match the list of columns mentioned along with GROUP BY.
Also,
Date >= ? AND Date <= ?
can be replaced by
Date BETWEEN ? AND ?
I'd recommend grouping by date as you fetch the results from the query.
while($row = $allDates->fetch()) {
$dates[$row['date'][] = $row;
}
That makes it easy to count the distinct dates
$count = count($dates);
And potentially simpler to format your output (headers/sections for each date, etc.)
foreach($dates as $date) {
foreach($date as $event) {
// ECHO ALL THE INFO IN A NICE TABLE
}
}
It does require iterating the same data twice, but for a reasonable amount of data to display on a page, that shouldn't make much difference.

Time Calculations with MySQL

I'm writing a time logging programme for a client who is a piano tuner, and I've written the following PHP code to give a record a status of 'to do':
$last_tuned = '2017-01-05';
$tuning_period = 3;
$month_last_tuned = date('Y-m', strtotime(date('Y-m-d', strtotime($last_tuned))));
$next_tuning = date('Y-m', strtotime($month_last_tuned.(' +'.$tuning_period.' months')));
if (time() > strtotime($next_tuning.' -1 months')) {
if (time() > strtotime($next_tuning)) {
return 'late';
} else {
return 'upcoming';
}
}
As you can see, the $last_tuned variable is of the date(YYYY-MM-DD) format. This is then converted to a (YYYY-MM) format.
Once convered, an additional number of months, identical to $tuning_period is then added to the $month_last_tuned variable giving us a month and year value for when we need to add a new record.
If the current time (found with time()) is greater than the $next_tuning variable - 1 month, it returns that the task is upcoming. If it's after the $next_tuning variable, it returns that the task is late.
I now have to write a MySQL query to list the items that would return as upcoming or late.
How would I write this in MySQL? I'm not very good with MySQL functions, and some help would be much appreciated.
My attempt at the logic is:
SELECT * FROM records
// The next lines are to get the most recent month_last_tuned value and add the tuning_period variable
WHERE
NOW() > (SELECT tuning_date FROM tunings ORDER BY tuning_date ASC LIMIT 1)
+
(SELECT tuning_period FROM records WHERE records.id = INITIAL CUSTOMER ID)
I know that that is completely wrong. The logic is pretty much there though.
My database schema is as follows:
I expect the rows returned from the query to be on-par with the 'late' or 'upcoming' values in the PHP Code above. This means that the rows returned will be within 1 months of their next tuning date (calculated from last tuning plus tuning period).
Thanks!
You'd probably be better off with using the DateTime object instead of manipulating date strings.
$last_tuned = '2017-01-05';
$tuning_period = 3; // months
$dt_last_tuned = DateTimeImmutable::createFromFormat('Y-m-d',$last_tuned);
$dt_next_tuning = $dt_last_tuned->add(new DateInterval('P3M'));
$dt_now = new DateTimeImmutable();
$dt_tuning_upcoming = $dt_next_tuning->sub(new DateInterval('P1M'));
if( $dt_now > $dt_next_tuning) {
return 'late';
}
if( $dt_now > $dt_tuning_upcoming) {
return 'upcoming';
}
You can also use these DateTime objects in your MySQL queries, by building the query and passing through something like $dt_next_tuning->format('Y-m-d H:i:s'); as needed.
Given your table structure, however, it may be easier to just get all the relevant records and process them. It's a little difficult to tell exactly how the pieces fit together, but generally speaking MySQL shouldn't be used for "processing" stuff.

How to transform a PHP loop to a MySQL query?

I am trying to make a PHP loop work for me in MySQL. Currently all visits to a website via a specific URL parameterare logged into a table along with the date and time of the visit. I am rebuilding the logging procedure to only count the visits via one specific parameter on one day, but I'll have to convert the old data first.
So here's what I'm trying to do: The MySQL table (let's call it my_visits) has 3 columns: parameter, visit_id and time.
In my PHP code, I've created the following loop to gather the data I need (all visits made via one paramter on one day, for all parameters):
foreach (range(2008, 2014) as $year) {
$visit_data = array();
$date_ts = strtotime($year . '-01-01');
while ($date_ts <= strtotime($year . '-12-31')) {
$date = date('Y-m-d', $date_ts);
$date_ts += 86400;
// count visit data
$sql = 'SELECT parameter, COUNT(parameter) AS total ' .
'FROM my_visits ' .
'WHERE time BETWEEN \''.$date.' 00:00\' AND \''.$date.' 23:59\' '.
'GROUP BY parameter ORDER BY total DESC';
$stmt = $db->prepare($sql);
$stmt->execute(array($date));
while ($row = $stmt->fetch()) {
$visit_data[] = array(
'param' => $row['parameter'],
'visit_count' => $row['total'],
'date' => $date);
}
$stmt->closeCursor();
}
}
Later on, the gathered data is inserted into a new table (basically eliminating visit_id) using a multiple INSERT (thanks to SO! :)).
The above code works, but due to the size of the table (roughly 3.4 million rows) it is very slow. Using 7 * 365 SQL queries just to gather the data seems just wrong to me and I fear the impact of just running the script will slow everything down substantially.
Is there a way to make this loop work in MySQL, like an equivalent query or something (on a yearly basis perhaps)? I've already tried a solution using GROUP BY, but since this eliminates either the specific dates or the parameters, I can't get it to work.
You can GROUP further.
SELECT `parameter`, COUNT(`parameter`) AS `total`, DATE(`time`) AS `date`
FROM `my_visits`
GROUP BY `parameter`, DATE(`time`)
You can then execute it once (instead of in a loop) and use $row['date'] instead of $date.
This also means you don't have to update your code when we reach 2015 ;)

PHP Comparing date in table with current date

I am trying to compare a stored date to the current date, and send back data accordingly. I'm not fluent in PHP so this is what I have so far
while ($row = mysql_fetch_array($result)) {
// temp user array
$event_date_str = $row["date"];
$todays_date_str = date("m-j-Y");
$today = strtotime($todays_date_str);
$event_date = strtotime($event_date_str);
if($event_date > $today){
$event = array();
$event["pid"] = $row["pid"];
$event["name"] = $row["name"];
$event["longitude"] = $row["longitude"];
$event["latitude"] = $row["latitude"];
$event["pavement"] = $row["pavement"];
$event["traffic"] = $row["traffic"];
$event["environment"] = $row["environment"];
$event["image_b64"] = $row["image_b64"];
$event["created_at"] = $row["created_at"];
$event["date"] = $row["date"];
$event["time"] = $row["time"];
$event["type"] = $row["type"];
// push single product into final response array
array_push($response["events"], $event);
}else{
//delete it here
}
With that code right there I am getting nothing back. I have 2 items stored in the database, One with the date "2-28-2014" and another with "2-14-2014", so I should be getting one back and not the other but I am getting neither back. I know that there are no leading zeros with the dates saved so I should use j right? Could someone help me figure out why this is not working, sorry if it seems like a simple question.
Thank you in advance,
Tyler
This is not an efficient way to do things. If you need to pick items based on date, do it in the MySQL query directly. PHP filtering will always be slower than MySQL. Especially since you have to deliver extra data over network when filtering at PHP level.
So do it like this:
SELECT * FROM `table` WHERE `record_expires_datetime_gmt` > UTC_TIMESTAMP();
SELECT * FROM `table` WHERE `record_expires_date_gmt` > DATE(UTC_TIMESTAMP());
// use NOW() for local time, UTC_TIMESTAMP() is GMT/UTC
Then do what you need to do with the records. Never SELECT * and then filter records in PHP.
There's a whole set of DATETIME functions in MySQL to allow you MySQL server side filtering of data.
PS: Obviously, for this method to work, your MySQL table has to be properly designed. Date (date and time) fields need to be of type DATE or DATETIME not surrogate strings that are meaningful only within your project.

How could I echo a random database field once a week using php and mySQL?

Google hasn't been much help sadly. I have some pseudo code below to give you an idea of what I'd like to achieve:
if ($time == one week) {
$result = mysql_query("SELECT * FROM table RANDOMLY,$connection");
echo $result[0];
}
I know I should be using mysqli, but I'm augumenting an existing (ageing) system. I'll be utilising mysqli in future, so if you could give me the solution using mysql that would be great!
I don't think it can be done with a single statement.
My best guess would be to use mysql_list_tables, select a random entry from that list and continue from there on.
Perhaps generating a random number between 1 and the value of SELECT Count(ID) from table. Then you have the index of the value you wish to output, you can simply run a SELECT statement for it; and output! :)
if ($time == one week) {
$result = mysql_query("SELECT * FROM table RANDOMLY,$connection");
echo $result[0];
}
Try this:
$weekNumber = date("W");
$result = mysqli_query("SELECT * FROM table RANDOMLY WHERE weeknumber = '\"$weekNumber\"', $connection");
echo $result[0];
}
Of course you’ll need a column in your table called weeknumber with 1 through 52 setup ahead of time.
As others pointed out you shouldn’t use mysql_* anything. I changed it to a mysqli_query.

Categories