Table structure is,
Table name: btq_user
name, email, kall, state_id, datain
Now i want to count number records which has kall = 1 or state_id in( 51, 20, 46) by weekly.
i need the results only by weekly (Mon - Sun) no matter if year changes. lets say for example 31-12-2012 is Monday and 6-1-2013 is Sunday so result should include that as a week. no matter if year changes it should count only with weeks.
This is what i tried but not working.
SELECT
count( if( A.state_id = 51 or A.state_id = 20 or A.state_id = 46, A.state_id,
null ) ) AS state_total, count( if( A.kall =1, A.kall, null ) ) AS appointment,
CONCAT( WEEK( A.datain) , " -
", YEAR( A.datain ) ) AS week
FROM btq_user A
GROUP BY week
ORDER BY A.datain ASC
also is there any possibility to display weeks ( 31-12-2012 - 6-1-2013 ) with results ?
Thank you for reading my question.
Two steps to this:
One, you need a week-truncate operation -- that will take your DATETIME item and give back midnight on the preceding Sunday (if your business rule says that the week begins on Sunday).
That becomes a suitable GROUP BY item. The WEEK() / YEAR() hack isn't suitable for this. It really makes a mess in the last/first week of each year.
In my experience, this exxpression will do the week-truncate trick for you, Sunday - Saturday,
FROM_DAYS(TO_DAYS(TIMESTAMP) -MOD(TO_DAYS(TIMESTAMP) -1, 7))
To get Monday - Sunday weeks, use this expression.
FROM_DAYS(TO_DAYS(TIMESTAMP) -MOD(TO_DAYS(TIMESTAMP) -2, 7))
So you can do this.
SELECT COUNT(whatever), SUM(whatelse),
FROM_DAYS(TO_DAYS(event_time) -MOD(TO_DAYS(event_time) -1, 7)) as WEEKSTART,
FROM TABLE
GROUP BY FROM_DAYS(TO_DAYS(event_time) -MOD(TO_DAYS(event_time) -1, 7))
How to group by week in MySQL?
Second, you need to add six days to that truncated date, so you can display the last day of each week along with the first day.
This is a good way to do that, with a nested query
SELECT whats, elses, weekstart, weekstart + INTERVAL 6 DAY AS weekend
FROM (
SELECT COUNT(whatever) AS whats, SUM(whatelse) AS elses,
FROM_DAYS(TO_DAYS(event_time) -MOD(TO_DAYS(event_time) -1, 7)) AS weekstart,
FROM TABLE
GROUP BY FROM_DAYS(TO_DAYS(event_time) -MOD(TO_DAYS(event_time) -1, 7))
) AS summary
ORDER BY weekstart
Doing lots of this? I suggest you create a stored TRUNC_WEEK function.
Related
I'm a WordPress (PHP) developer, however I have not done a lot of complex MySQL queries. I am working on an events website and want to create filters: User should be able to filter events by the following criteria: TODAY, TOMORROW, THIS WEEKEND, NEXT 7 DAYS, CHOOSE YOUR DATES.
Meta data used to filter events is below and can be found in the post meta db table in the meta_key column.
start_date
end_date
times
recurring_event
days
sold_out
cancelled
This is how the table looks like:
id post_id meta_key meta_value
1 12 start_date 20140923
2 22 days a:4:{i:0;s:6:"monday";i:1;s:9:"wednesday";i:2;s:6:"friday";i:3;s:8:"saturday"}
3 12 end_date 20141003
4 78 recurring_event 0
5 34 times 24 Hours
6 12 days a:2:{i:0;s:6:"monday";i:1;s:7:"tuesday";}
7 67 start_date 20140906
8 45 end_date 20141108
What MySQL queries can I use to get events for Today, Tomorrow, Weekend and 7 days.
I do not know SQL enough, then, for this case, I would caculate the date with PHP and then make the query with SQL.
These are the pages that helped me for the following :
http://php.net/manual/fr/function.date.php
http://php.net/manual/fr/function.mktime.php
1) For today, no calculation, just get the date of today and make the query :
<?php
$today = date('Y-m-d',mktime());
$result = mysqli_query($connect, 'SELECT * FROM events WHERE start_date = "'.$today.'"');
?>
2) Tomorow, calculate tomorow date :
<?php
$tomorow = date('Y-m-d',mktime(0, 0, 0, date("m") , date("d")+1, date("Y")));
$result = mysqli_query($connect, 'SELECT * FROM events WHERE start_date = "'.$tomorow.'"');
?>
3) 7 days later, calculate 7 days later date :
<?php
$day7 = date('Y-m-d',mktime(0, 0, 0, date("m") , date("d")+7, date("Y")));
$result = mysqli_query($connect, 'SELECT * FROM events WHERE start_date = "'.$day7.'"');
?>
3) Week end, calculte when week end comes :
For this one, I cannot write it so quickly, sorry.
Explanations :
date('Y-m-d',mktime()); gives 2014-09-04 in $today.
Because mktime() is empty, so mktime() is based on the server time, no argument.
Y => year like ####
m => month like ##
d => day like ##
date('Y-m-d',mktime(0, 0, 0, date("m") , date("d")+1, date("Y"))); gives 2014-09-05
This time we gave arguments to mktime(), 0 hour, 0 minutes, 0 second, 09, 04+1, 2014.
date("m") = 09
date("d")+1 = 04+1 = 05
date("Y") = 2014
I hope this might help you.
I'm sorry, but I don't know how put PHP in Wordpress.
Nils.
For the weekend and next 7 days you may have to mix sql and php
Today
SELECT * from tablename where start_date=CURDATE();
Tomorrow
SELECT * from tablename where start_date = CURDATE()+INTERVAL 1 DAY;
For Weekend you have to find the weekend dates first.
using
SELECT DAYOFWEEK
you can find current day . So if you have an array , match with it and
add how many days to reach Saturday and sunday.
I dont know any other easy way
For next 7 days
SELECT * from tablename where start_date >= CURDATE() and
start_date=< CURDATE()+INTERVAL 7 DAY;
Use the tutorial and try yourself :-)
http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_curdate
Say I've got a simple mysql table with columns id, title, date. Each day can have several rows, but not every day has a row. For example there might be 5 rows with June 15th as the date, but zero rows with June 14th as the date. How can I get all results from the past 7 days, excluding June 14th because it has no rows. So I'm not just subtracting 7 days from the current date. I want to get only the past 7 days which have any rows at all.
Then I want to return each day as a single result, like a group by date, but by the year/month/day part of the timestamp, so that what I get back is only 7 results, and each result is like an array of individual rows.
I hope that makes sense. Any ideas?
Edit:
I realized I can do it something like this:
WHERE Date BETWEEN DATE_SUB( NOW(), INTERVAL DATEDIFF( NOW(), (SELECT Date FROM test GROUP BY Date LIMIT 7,1 ) ) DAY ) and NOW()
But this gives an SQL syntax error. What I'm trying to do is a subquery and group by date to get one result for each day, and return one result starting at offset 7, then do a DATEDIFF on that to get the number of days that DATE_SUB should put in the INTERVAL x DAYS
You won't be able to get the same day results back as an array, but you can group it by date, with the titles comma separated:
SELECT GROUP_CONCAT(title) AS titles, date
FROM test
WHERE date > DATE_SUB(CURDATE(), INTERVAL 7 DAY)
GROUP BY date;
Then in PHP, do something like this:
foreach ($results as $row)
{
echo $row['date'];
foreach ($row['titles'] as $title)
{
echo $title;
}
}
Figured it out: It works!
WHERE Date BETWEEN DATE_SUB(NOW(), INTERVAL (DATEDIFF( NOW(), (SELECT Date FROM test GROUP BY Date ORDER BY Date DESC LIMIT 8,1 ) ) ) DAY) and NOW()
I was missing a parentheses, and I had to add ORDER BY and DESC to the subquery.
I have a fun one for you. I have a database with the date columns free_from and free_until. What I need to find is the amount of days between now and 1 month today which are free. For example, if the current date was 2013/01/15 and the columns were as follows:
free_from | free_until
2013/01/12| 2013/01/17
2013/01/22| 2013/01/26
2013/01/29| 2013/02/04
2013/02/09| 2013/02/11
2013/02/14| 2013/02/17
2013/02/19| 2013/02/30
The answer would be 16
as 2 + 4 + 6 + 2 + 2 + 0 = 16
The first row only starts counting at the 15th rather than the 12th
since the 15th is the current date.
The last row is discounted because none of the dates are within a
month of the current date.
The dates must be counted as it the free_from date is inclusive and
the free_until date is exclusive.
I'm assuming DATEDIFF() will be used somewhere along the line, but I can't, for the life of me, work this one out.
Thanks for your time!
Edit: This is going into PHP mysql_query so that might restrict you a little concerning what you can do with MYSQL.
SET #today = "2013-01-15";
SET #nextm = DATE_ADD(#today, INTERVAL 1 month);
SET #lastd = DATE_ADD(#nextm, INTERVAL 1 day);
SELECT
DATEDIFF(
IF(#lastd> free_until, free_until, #lastd),
IF(#today > free_from, #today, free_from)
)
FROM `test`
WHERE free_until >= #today AND free_from < #nextm
That should work. At least for your test data. But what day is 2013/02/30? :-)
Dont forget to change #today = CURDATE();
The best I can think of is something like:
WHERE free_until > CURDATE()
AND free_from < CURDATE() + INTERVAL '1' MONTH
That will get rid of any unnecessary rows. Then on the first row do in PHP:
date_diff(date(), free_until)
On the last row, do:
date_diff(free_from, strtotime(date("Y-m-d", strtotime($todayDate)) . "+1 month"))
Then on intermediate dates do:
date_diff(free_from, free_until)
Something to that effect, but this seems extremely clunky and convoluted...
From the top of my mind... first do a:
SELECT a.free_from AS a_from, a.free_until AS a_until, b.free_from AS b_from
FROM availability a
INNER JOIN availability b ON b.free_from > a.free_until
ORDER BY a_from, b_from
This probably will return a set of rows where for each row interval you have next i.e. greater intervals. The results are ordered strategically. You can then wrap the results in a partial group by:
SELECT * FROM (
SELECT a.free_from AS a_from, a.free_until AS a_until, b.free_from AS b_from
FROM availability a
INNER JOIN availability b ON b.free_from > a.free_until
ORDER BY a_from, b_from
) AS NextInterval
GROUP BY a_from, b_until
In the above query, add a DATE_DIFF clause (wrap it in SUM() if necessary):
DATE_DIFF(b_until, a_from)
> Start stop pinnumber
> ---------------------------------------------------------
> 2012-03-14 13:22:17 2012-03-14 15:22:50 2001
> 2012-03-14 18:11:10 2012-03-14 19:10:10 2001
> 2012-03-15 07:20:10 2012-03-15 13:20:50 2001
>**2012-03-16 19:21:55 2012-03-17 02:55:22 2001** //on 16(19:21:55
to 23:59:59) and
//on 17(00 to 02:55:22)
> 2012-03-17 14:15:05 2012-03-17 17:44:50 2001
> 2012-03-18 19:11:10 2012-03-18 19:10:10 2002
> 2012-03-18 10:20:10 2012-03-18 13:20:50 2003
> 2012-03-18 11:20:10 2012-03-18 15:11:50 2001
Question:
How can I calculate total time of each user of per day ('start', 'stop') per day? Please see the above highlighted point. Suppose, If user 'start' today and stop it tomorrow then today hour are different and tomorrow hour are different?
right now i am using following query:-
SELECT SEC_TO_TIME( SUM( TIME_TO_SEC(TIMEDIFF(stop ,start ) ) ) ) AS time1, clock. * FROM table_name WHERE pin_number = '2001' GROUP BY DATE_FORMAT( start , '%W %M %Y' )
from above query i am getting per day records but when start date and stop date is different. it calculate total time not single day time but i need per day time.
I think I finally got there. You first need to get a set of days, which I obtain through a subquery that takes a UNION of the start and stop times (you could filter this for your pinnumber if desired in order to reduce the size of the JOIN).
One then joins each such date with those (start,stop) pairs that encompass that date (i.e. either start during the day, or the start of the day is between the start and stop time).
Finally, one groups by day and takes the sum of the amount of time between the start and end times, cutting off at the day start and end as appropriate (the magic 86400 is the number of seconds in a day = 24*60*60). Sadly this won't play nice with daylight savings, leap seconds, etc...
SELECT FROM_UNIXTIME(unixday, '%d/%m/%Y'), SUM(
LEAST( unixday+86400, UNIX_TIMESTAMP(Stop ))
- GREATEST(unixday , UNIX_TIMESTAMP(Start))
) AS Seconds
FROM table_name JOIN (
SELECT UNIX_TIMESTAMP(DATE(Start)) AS unixday FROM table_name
UNION
SELECT UNIX_TIMESTAMP(DATE(Stop )) AS unixday FROM table_name
) AS days ON (
UNIX_TIMESTAMP(Start) BETWEEN unixday AND unixday+86400
OR (unixday BETWEEN UNIX_TIMESTAMP(Start) AND UNIX_TIMESTAMP(Stop))
)
WHERE pinnumber = 2001
GROUP BY unixday;
See it on sqlfiddle.
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