Converting date inside loop breaks loop - php

So I have a MySQL query feeding into an array to sanitize it to meet the needs of a graphing library. I tried to convert the hours, currently in 24h format, to a slightly prettier 12h format. But when I do, it kills the loop after one iteration, and so only one value gets fed to the graph (instead of values for the whole day). I'm very new to PHP, so I'm not sure what is breaking this. What is the proper way to accomplish what I need to do?
$data = $conn->query('SELECT HOUR( TIMESTAMP ) AS HOUR , COUNT( DISTINCT detected_key ) AS num_rows
FROM Visitors
WHERE TIMESTAMP >= NOW( ) - INTERVAL 1 DAY
GROUP BY HOUR( TIMESTAMP )
ORDER BY `id` ASC
LIMIT 0 , 24');
while($row = $data->fetch(PDO::FETCH_ASSOC)) {
$prettytime = DATE("g:i a", STRTOTIME($row['HOUR']));
$visit[$prettytime]= $row['num_rows'];
}
EDIT: I tried using DATE_FORMAT already, but that still just outputs as the 24h format. The only documentation I found on the MySQL website always had it right after SELECT.
SELECT DATE_FORMAT( TIMESTAMP, "%l:%i %a" ) , HOUR( TIMESTAMP ) AS HOUR , COUNT( DISTINCT detected_key ) AS num_rows
FROM Visitors
WHERE TIMESTAMP >= NOW( ) - INTERVAL 1 DAY
GROUP BY HOUR( TIMESTAMP )
ORDER BY `id` ASC
LIMIT 0 , 24
Here is the website where you can see the graph. The one using the query above is the top left graph.

This would seem to work rather nicely from a database field defined as
`thetime` timestamp NULL DEFAULT CURRENT_TIMESTAMP
Then using the MySQL DATE_FORMAT() and HOUR() functions :-
SELECT thetime, DATE_FORMAT(thetime, '%r') from test_table
Gives the results :-
2014-01-20 10:57:45, 10:57:45 AM
2014-01-20 23:59:31, 11:59:31 PM
Or
SELECT thetime, HOUR(DATE_FORMAT(thetime, '%r')) from test_table
Gives :-
2014-01-20 10:57:45, 10
2014-01-20 23:59:31, 11

Related

Group by day but one result set for hours less than 16:00 and one for hours higher than 16:00

I'm stuck again!
I got the following query to select a result set of customers and some dates.
SELECT
COUNT( DISTINCT CASE `saved` WHEN 0 THEN `cust` ELSE NULL END ) AS test,
COUNT( DISTINCT CASE `saved` WHEN 1 THEN `cust` ELSE NULL END ) AS test2,
DATE( `date` ) AS dateday
FROM `salg_test`
WHERE `is_void` = 0
GROUP BY dateday
ORDER BY dateday DESC
This can be seen as a total. Now I would like to separate this query based on the following condition: hour is less than 16:00 or more than 16:00. Im using PHPmyadmin, the date field is current_timestamp format, 24H clock. The problem is that i still need it to be grouped on DATE and not current_timestamp. If Possible I would like to excend my SQL query rather than using PHP, though i am echo'ing the query in to a table with a while loop. Any tips?
Thanks so much for your help!
Have you tried adding in another item to your select query along the lines of the statement below?
IF(DATE_FORMAT(dateday, '%H') < 16,0,1) AS afterfourpm
The statement should get the hour in 24 hour format in the dateday column and produce the result of 0 if its less than 16 and 1 if its above 16.
Then you can perhaps sort on that value? Without having the database it's hard to test this but it'd look a bit like
SELECT COUNT( DISTINCT
CASE `saved`
WHEN 0
THEN `cust`
ELSE NULL
END ) AS test, COUNT( DISTINCT
CASE `saved`
WHEN 1
THEN `cust`
ELSE NULL
END ) AS winback, DATE( `date` ) AS test1,
IF(DATE_FORMAT(dateday, '%H') < 16,0,1) AS afterfourpm
FROM `salg_test`
WHERE `is_void` =0
GROUP BY dateday
ORDER BY dateday DESC, afterfourpm DESC
I hope that makes sense.
You should write another case statement into the SQL that you group on.
For instance:
Case when timestamp <= 16:00 then 'Y' else 'N' end as time_flag
Group on that also and it should get you the output that you want.

If the returned records are less than x do another query efficiency

So I have one main query that selects records from today. And if today's records are less than 3, then perform another query. This is how I'm currently doing it:
<?php
//Select today's records
$select = mysql_query("SELECT * FROM item_info
WHERE item_info.content_time
BETWEEN UNIX_TIMESTAMP( CURDATE( ) )
AND UNIX_TIMESTAMP( CURDATE( ) + INTERVAL 1
DAY )
ORDER BY item_info.item_id DESC
LIMIT 9 ");
//If today's records are less than 3, select the ones from yesterday
if(mysql_num_rows($select) < 3){
$select = mysql_query("SELECT * FROM item_info
WHERE item_info.content_time
BETWEEN UNIX_TIMESTAMP( CURDATE( ) + INTERVAL -1
DAY )
AND UNIX_TIMESTAMP( CURDATE( ) )
ORDER BY item_info.item_id DESC
LIMIT 9 ");
}
//Fetch $select
?>
My main concerns are:
-Is this a proper use of mysql_num_rows?
-Are there other ways to do this?
Usually there will be more than 3 records each day, so the subquery is just in case.
Yes there is another way to do it. Run your query to always select the top 9 records from yesterday and today. Modify your order by clause so that it looks like this:
order by item_info.content_time desc, item_info.item_id desc
That way you don't need to check the number of records returned.
However, this is slightly different logic. It will give a different answer than what you currently have if the number of records for today is between 3 and 9. If that's not ok, keep what you have.
By the way, you should verify that your current code does meet your requirements. Currently, if you get 1 or 2 records from today, you ignore them and use yesterday's data. That might not be what you had in mind.

php query returns missing data

I have a problem with my php query for getting data. Let me explain what I want. I have a database which saves the active users for some hours in a day. For example 01-01-2012 13:00
active = 5 01-01-2012 14:00 active = 10. My php query should make an array which contains 2 columns which are date and active. But date must be like 01-01-2012 withour hours. So I grouped them as date2 but I couldn't find the active(sum) for each days. Here is my query which doesn't give the right active sums.
$query2 = mysql_query("SELECT DATE_FORMAT(date, '%Y-%m-%d') AS date2, SUM(active) FROM hit WHERE game= '".$game."' AND source = '".$source."' AND date > '".$dateFrom."' AND date < '".$dateTo."' GROUP BY date2 ORDER BY date2");
while($tuple= mysql_fetch_array($query2)){
$myArr[] = $tuple;
}
print_r($myArr);
You need to alias SUM(active) and GROUP BY date. If date is a datetime, you can GROUP BY DATE(date). Also, be careful with reserved words and make sure to use tick marks where necessary.
Further, if you are looking for an inclusive date range, you should know that when you send 2012-10-03, it is actually 2012-10-03 00:00:00 and any records between 00:00:00 and 23:59:59 will be ignored.
Lastly, you should stop using mysql_ functions as they are being deprecated.
This query should work:
$query2 = mysql_query("
SELECT DATE_FORMAT(`date`, '%Y-%m-%d') AS date2, SUM(active) AS active
FROM hit
WHERE game= '".$game."' AND source = '".$source."'
AND `date` > DATE_SUB('".$dateFrom."', INTERVAL 1 SECOND)
AND `date` < DATE_ADD('".$dateTo."', INTERVAL 1 DAY)
GROUP BY DATE(`date`)
ORDER BY `date`");
$i = 0;
while($tuple= mysql_fetch_array($query2)){
$myArr[$i][date2] = $tuple[date2];
$myArr[$i][active] = $tuple[active];
$i++;
}
print_r($myArr);
Your array ($myArr) would then look something like:
Array
(
[0] => Array
(
[date2] => 2012-10-03
[active] => 5
)
)

Assistance with MySQL query & timestamps

I am trying to create a query which returns offers for all rows that belong to a club_id that are within a start_date & end_date, however the query should also return results for any that match the club_id AND the end_date is 0 - any ideas of how to do this?
My current query is below...
SELECT
* ,
UNIX_TIMESTAMP( start_date ) AS start_dateStamp,
UNIX_TIMESTAMP( end_date ) AS end_dateStamp
FROM
(`offers`)
WHERE
UNIX_TIMESTAMP( `start_date` ) <1329308797
AND
UNIX_TIMESTAMP( `end_date` ) >1329308797
AND
`club_id` =23
SELECT
`offers`.* ,
UNIX_TIMESTAMP( start_date ) AS start_dateStamp,
UNIX_TIMESTAMP( end_date ) AS end_dateStamp
FROM `offers`
WHERE `club_id` =23
AND (
(
`start_date`<FROM_UNIXTIME(1329308797)
AND `end_date`>FROM_UNIXTIME(1329308797)
)
OR `end_date`=FROM_UNIXTIME(0)
)
Please note, that I moved the convertion from unix-timestamp to MySQL-date from the field to the constant - this way it has to be converted only once, and not for all rows. Additionally this way an index can be used.
Edit
With "date zero" not being Unix-Zero but MySQL-Zeor the last line should be
OR `end_date`='0000-00-00'
additionally, if the data type of start_date and end_date is not DATETIME but DATE you need
DATE(FROM_UNIXTIME(...))
instead of
FROM_UNIXTIME(...)
you didn't specify if end_date = 0 also reqires start_date > $timestamp, so I assumed start_date still has to meet its criteria.
SELECT
please_name,
the_columns,
you_want_to_select_seperately,
for_reasons,
UNIX_TIMESTAMP(start_date) AS start_dateStamp,
UNIX_TIMESTAMP(end_date) AS end_dateStamp
FROM `offers`
WHERE `club_id` = 23
AND `start_date`) < UNIX_TIMESTAMP(1329308797)
AND (`end_date` > UNIX_TIMESTAMP(1329308797) OR `end_date` = "0000-00-00")
Some notes on your SQL:
don't use * unless you have reasons. which, judging from your original question, you really really don't
try to format your code so it's readable
sort your conditions so the condition selecting the fewest records (read: has the highest specificity) comes first. This has become more a mental thing than hinting the MySQL optimizer, it's still a convention I'd stick to
if you compare (date) ranges, consider using col BETWEEN min_val and max_val (boundaries are inclusive)
don't use functions in the WHERE and GROUP BY clause. MySQL cannot cache/index these and must thus run the value of every row through that function. UNIX_TIMESTAMP() has a friend named FROM_UNIXTIME() - doing pretty much the exact opposite. This is nothing you can't do yourself in PHP with date(), though.
consider setting fields to NULL if they have an unknown value. You either have a date (2012-02-15) or you don't (NULL). That would allow your query to simply check OR end_date IS NULL
Is this what you want?
SELECT *
, UNIX_TIMESTAMP(start_date) AS start_dateStamp
, UNIX_TIMESTAMP(end_date) AS end_dateStamp
FROM
`offers`
WHERE
(UNIX_TIMESTAMP(`start_date`) < 1329308797 AND UNIX_TIMESTAMP(`end_date`) > 1329308797)
OR
(`club_id` = 23 AND `end_date` = '0000-00-00 00:00:00')

How to minimize the load in queries that need grouping with different invervals?

I'm looking for a best practice advice how to speed up queries and at the same time to minimize the overhead needed to invoke date/mktime functions. To trivialize the problem I'm dealing with the following table layout:
CREATE TABLE my_table(
id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT,
important_data INTEGER,
date INTEGER);
The user can choose to show 1) all entries between two dates:
SELECT * FROM my_table
WHERE date >= ? AND date <= ?
ORDER BY date DESC;
Output:
10-21-2009 12:12:12, 10002
10-21-2009 14:12:12, 15002
10-22-2009 14:05:01, 20030
10-23-2009 15:23:35, 300
....
I don't think there is much to improve in this case.
2) Summarize/group the output by day, week, month, year:
SELECT COUNT(*) AS count, SUM(important_data) AS important_data
FROM my_table
WHERE date >= ? AND date <= ?
ORDER BY date DESC;
Example output by month:
10-2009, 100002
11-2009, 200030
12-2009, 3000
01-2010, 0 /* <- very important to show empty dates, with no entries in the table! */
....
To accomplish option 2) I'm currently running a very costly for-loop with mktime/date like the following:
for(...){ /* example for group by day */
$span_from = (int)mktime(0, 0, 0, date("m", $time_min), date("d", $time_min)+$i, date("Y", $time_min));
$span_to = (int)mktime(0, 0, 0, date("m", $time_min), date("d", $time_min)+$i+1, date("Y", $time_min));
$query = "..";
$output = date("m-d-y", ..);
}
What are my ideas so far? Add additional/ redundant columns (INTEGER) for day (20091212), month (200912), week (200942) and year (2009). This way I can get rid of all the unnecessary queries in the for loop. However I'm still facing the problem to very fastly calculate all dates that doesn't have any equivalent in database. One way to simply move the problem could be to let MySQL do the job and simply use one big query (calculate all the dates/use MySQL date functions) with a left join (the data). Would it be wise to let MySQL take the extra load? Anyway I'm reluctant to use all these mktime/date in the for loop. Since I have complete control over the table layout and code even suggestions with major changes are welcome!
Update
Thanks to Greg I came up with the following SQL query. However it still bugs me to use 50 lines of sql statements - build up with php - that maybe could be done faster and more elegantly otherwise:
SELECT * FROM (
SELECT DATE_ADD('2009-01-30', INTERVAL 0 DAY) AS day UNION ALL
SELECT DATE_ADD('2009-01-30', INTERVAL 1 DAY) AS day UNION ALL
SELECT DATE_ADD('2009-01-30', INTERVAL 2 DAY) AS day UNION ALL
SELECT DATE_ADD('2009-01-30', INTERVAL 3 DAY) AS day UNION ALL
......
SELECT DATE_ADD('2009-01-30', INTERVAL 50 DAY) AS day ) AS dates
LEFT JOIN (
SELECT DATE_FORMAT(date, '%Y-%m-%d') AS date, SUM(data) AS data
FROM test
GROUP BY date
) AS results
ON DATE_FORMAT(dates.day, '%Y-%m-%d') = results.date;
You definitely shouldn't be doing a query inside a loop.
You can group like this:
SELECT COUNT(*) AS count, SUM(important_data) AS important_data, DATE_FORMAT('%Y-%m', date) AS month
FROM my_table
WHERE date BETWEEN ? AND ? -- This should be the min and max of the whole range
GROUP BY DATE_FORMAT('%Y-%m', date)
ORDER BY date DESC;
Then pull these into an array keyed by date and loop over your data range as you are doing (that loop should be pretty light on CPU).
Another idea is not to use string inside the query. Transform the string parameter to datetime, on mysql.
STR_TO_DATE(str,format)
http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html

Categories