Group mysql query by 15 min intervals - mysql

I've got a monitoring system that is collecting data every n seconds (n is approximately 10 but varies). I'd like to aggregate the collected data by 15 minute intervals. Is there a way to consolidate the timestamp values into 15 minute chunks to allow for grouping to work?

SELECT FLOOR(UNIX_TIMESTAMP(timestamp)/(15 * 60)) AS timekey
FROM table
GROUP BY timekey;

Try this , grouping of records of 15 minutes interval, you can change 15*60 to the interval in seconds you need
SELECT sec_to_time(time_to_sec(datefield)- time_to_sec(datefield)%(15*60)) as intervals from tablename
group by intervals

Adaptation of approach 1) below:
select Round(date_format(date, "%i") / (15*60)) AS interval
from table
group by interval
Adaptation of approach 3) below:
SELECT Round(Convert(substring(date_column, 14, 2), UNSIGNED) / (15*60)) AS interval /* e.g. 2009-01-04 12:20:00 */
FROM table
GROUP BY interval;
A few approaches I've found here:
1)
select date_format(date, "%W") AS `Day of the week`, sum(cost)
from daily_cost
group by `Day of the week`
order by date_format(date, "%w")
2)
select count(*) as 'count',
date_format(min(added_on), '%Y-%M-%d') as 'week commencing',
date_format(added_on, '%Y%u') as 'week'
from system
where added_on >= '2007-05-16'
group by week
order by 3 desc;
3)
SELECT substring(postdate, 1,10) AS dd, COUNT(id) FROM MyTable GROUP BY dd;
(Also here: http://www.bradino.com/mysql/dayparting-on-datetime-field-using-substring/)
EDIT: All the solutions will perform badly on a table with a large number of records.

I started with the answer given above by unutbu but didn't get what I needed and had to add a bit to it.
Select Created, from_unixtime(FLOOR(UNIX_TIMESTAMP(Created)/(15*60))*(15*60)) GroupTime,
COUNT(*) as Cnt
FROM issue i
GROUP BY GroupTime
This code divides by the 900 seconds in a 15 minute span then floors the value and multiplies it back up by 900, essentially rounding down to the nearest 15 minute increment.

Following query groups rows and creates timestamps at 15 min intervals.
Select concat( date(created_dt) , ' ', sec_to_time(time_to_sec(created_dt)- time_to_sec(created_dt)%(15*60) + (15*60)))as created_dt_new from table_name group by created_dt_new
E.g Timestamps
2016-11-09 13:16:29
2016-11-09 13:16:49
2016-11-09 13:17:06
2016-11-09 13:17:26
2016-11-09 13:18:24
2016-11-09 13:19:59
2016-11-09 13:21:17
Are grouped into 2016-11-09 13:30:00
sec_to_time(time_to_sec(created_dt)- time_to_sec(created_dt)%(15*60) + (15*60)))
Upper bounds time to nearest 15 min interval. e.g 12:10 -> 12:15
concat( date(created_dt) , ' ', sec_to_time(time_to_sec(created_dt)- time_to_sec(created_dt)%(15*60) + (15*60)))
Generates a timestamp taking the date from the timestamp field.

Unix timestamps: floor them to nearest 15 minute using one of the following:
timestamp div (15 * 60) * (15 * 60) -- div is integer division operator
timestamp - timestamp % (15 * 60)
Date time: assuming the datatype does not have fractional seconds, floor them to nearest 15 minute using:
date - INTERVAL EXTRACT(SECOND FROM date) SECOND - INTERVAL EXTRACT(MINUTE FROM date) % 15 MINUTE
DBFiddle

This worked for me
mysql> **SELECT FROM_UNIXTIME(UNIX_TIMESTAMP(NOW())- UNIX_TIMESTAMP(NOW())%(15*60));**
+---------------------------------------------------------------------+
| FROM_UNIXTIME(UNIX_TIMESTAMP(NOW())- UNIX_TIMESTAMP(NOW())%(15*60)) |
+---------------------------------------------------------------------+
| 2012-02-09 11:15:00 |
+---------------------------------------------------------------------+
1 row in set (0.00 sec)

THis Work for me
SELECT CONCAT (
YEAR(transactionDate)
,'-'
,MONTH(transactionDate)
,'-'
,DAYOFMONTH(transactionDate)
,' '
,HOUR(transactionDate)
,':'
,((floor((MINUTE(transactionDate) / 15)) + 1) * 15) - 1
,':59'
) AS tmp1
,count(*)
FROM tablename
GROUP BY tmp1 limit 20;

Change "15" to whatever interval you want.
select count(*),
CONCAT(HOUR(col_date),":",(MINUTE(create_date) div 15)*15) as date
from tablename
GROUP BY date
ORDER BY col_date ASC;

I was not satisfied by GROUP BY.
SELECT datetime
FROM table
WHERE MOD(MINUTE(TIME(datetime)),15) = 0 AND SECOND(TIME(datetime)) = 0;

Related

SELECT to count events by intervals [duplicate]

I've got a monitoring system that is collecting data every n seconds (n is approximately 10 but varies). I'd like to aggregate the collected data by 15 minute intervals. Is there a way to consolidate the timestamp values into 15 minute chunks to allow for grouping to work?
SELECT FLOOR(UNIX_TIMESTAMP(timestamp)/(15 * 60)) AS timekey
FROM table
GROUP BY timekey;
Try this , grouping of records of 15 minutes interval, you can change 15*60 to the interval in seconds you need
SELECT sec_to_time(time_to_sec(datefield)- time_to_sec(datefield)%(15*60)) as intervals from tablename
group by intervals
Adaptation of approach 1) below:
select Round(date_format(date, "%i") / (15*60)) AS interval
from table
group by interval
Adaptation of approach 3) below:
SELECT Round(Convert(substring(date_column, 14, 2), UNSIGNED) / (15*60)) AS interval /* e.g. 2009-01-04 12:20:00 */
FROM table
GROUP BY interval;
A few approaches I've found here:
1)
select date_format(date, "%W") AS `Day of the week`, sum(cost)
from daily_cost
group by `Day of the week`
order by date_format(date, "%w")
2)
select count(*) as 'count',
date_format(min(added_on), '%Y-%M-%d') as 'week commencing',
date_format(added_on, '%Y%u') as 'week'
from system
where added_on >= '2007-05-16'
group by week
order by 3 desc;
3)
SELECT substring(postdate, 1,10) AS dd, COUNT(id) FROM MyTable GROUP BY dd;
(Also here: http://www.bradino.com/mysql/dayparting-on-datetime-field-using-substring/)
EDIT: All the solutions will perform badly on a table with a large number of records.
I started with the answer given above by unutbu but didn't get what I needed and had to add a bit to it.
Select Created, from_unixtime(FLOOR(UNIX_TIMESTAMP(Created)/(15*60))*(15*60)) GroupTime,
COUNT(*) as Cnt
FROM issue i
GROUP BY GroupTime
This code divides by the 900 seconds in a 15 minute span then floors the value and multiplies it back up by 900, essentially rounding down to the nearest 15 minute increment.
Following query groups rows and creates timestamps at 15 min intervals.
Select concat( date(created_dt) , ' ', sec_to_time(time_to_sec(created_dt)- time_to_sec(created_dt)%(15*60) + (15*60)))as created_dt_new from table_name group by created_dt_new
E.g Timestamps
2016-11-09 13:16:29
2016-11-09 13:16:49
2016-11-09 13:17:06
2016-11-09 13:17:26
2016-11-09 13:18:24
2016-11-09 13:19:59
2016-11-09 13:21:17
Are grouped into 2016-11-09 13:30:00
sec_to_time(time_to_sec(created_dt)- time_to_sec(created_dt)%(15*60) + (15*60)))
Upper bounds time to nearest 15 min interval. e.g 12:10 -> 12:15
concat( date(created_dt) , ' ', sec_to_time(time_to_sec(created_dt)- time_to_sec(created_dt)%(15*60) + (15*60)))
Generates a timestamp taking the date from the timestamp field.
Unix timestamps: floor them to nearest 15 minute using one of the following:
timestamp div (15 * 60) * (15 * 60) -- div is integer division operator
timestamp - timestamp % (15 * 60)
Date time: assuming the datatype does not have fractional seconds, floor them to nearest 15 minute using:
date - INTERVAL EXTRACT(SECOND FROM date) SECOND - INTERVAL EXTRACT(MINUTE FROM date) % 15 MINUTE
DBFiddle
This worked for me
mysql> **SELECT FROM_UNIXTIME(UNIX_TIMESTAMP(NOW())- UNIX_TIMESTAMP(NOW())%(15*60));**
+---------------------------------------------------------------------+
| FROM_UNIXTIME(UNIX_TIMESTAMP(NOW())- UNIX_TIMESTAMP(NOW())%(15*60)) |
+---------------------------------------------------------------------+
| 2012-02-09 11:15:00 |
+---------------------------------------------------------------------+
1 row in set (0.00 sec)
THis Work for me
SELECT CONCAT (
YEAR(transactionDate)
,'-'
,MONTH(transactionDate)
,'-'
,DAYOFMONTH(transactionDate)
,' '
,HOUR(transactionDate)
,':'
,((floor((MINUTE(transactionDate) / 15)) + 1) * 15) - 1
,':59'
) AS tmp1
,count(*)
FROM tablename
GROUP BY tmp1 limit 20;
Change "15" to whatever interval you want.
select count(*),
CONCAT(HOUR(col_date),":",(MINUTE(create_date) div 15)*15) as date
from tablename
GROUP BY date
ORDER BY col_date ASC;
I was not satisfied by GROUP BY.
SELECT datetime
FROM table
WHERE MOD(MINUTE(TIME(datetime)),15) = 0 AND SECOND(TIME(datetime)) = 0;

Elapsed Time Between Two Dates for specified time range

I have a MYSQL table with a TIMESTAMP column 'Start' and a TIMESTAMP column 'End'. I want to return the number of minutes between the start and the end (End is always after than Start). Usually I'd just use 'TIMESTAMPDIFF()' but this time I need to get the minutes from 9am until 22pm, of each day in that date range.
If a row has a Start '2017-01-01 07:15:00' and an End of '2017-01-02 11:30:00' - the elapsed time should be 15.5 hours (930 minutes).
I'm having trouble coming up with a decent way of doing this and my searching online hasn't found quite what I'm looking for. Can someone help me along?
Edit:
CREATE TABLE date_ranges (
Start TIMESTAMP,
End TIMESTAMP
);
INSERT INTO date_ranges VALUES('2017-01-01 07:15:00','2017-01-02 11:30:00');
I came up with this:
SELECT Start, End, TIMESTAMPDIFF(MINUTE, Start, End) AS MinutesElapsed
FROM date_ranges;
I'm missing the part where the time in minutes is calculated only in the specified time range (9am until 22pm). Any ideas?
Here you go:
SELECT t1, t2, (TIMESTAMPDIFF(MINUTE, t1, t2) - TIMESTAMPDIFF(DAY, t1, t2)*660) FROM
(SELECT CASE WHEN t1 < STR_TO_DATE(concat(date_format(t1, '%Y-%m-%d'), ' 09:00:00'), '%Y-%m-%d %h:%i:%s')
THEN STR_TO_DATE(concat(date_format(t1, '%Y-%m-%d'), ' 09:00:00'), '%Y-%m-%d %h:%i:%s')
ELSE t1
END AS t1 FROM test) test1,
(SELECT CASE WHEN t2 > STR_TO_DATE(concat(date_format(t2, '%Y-%m-%d'), ' 22:00:00'), '%Y-%m-%d %h:%i:%s')
THEN STR_TO_DATE(concat(date_format(t2, '%Y-%m-%d'), ' 22:00:00'), '%Y-%m-%d %h:%i:%s')
ELSE t2
END AS t2 FROM test) test2;
660 = number of minutes between 22:00 and 09:00 (11 hours)
Here's the SQL Fiddle.
It's not very concise, but this should give you the results you want:
select started_at,ended_at,
(case
when date(ended_at) = date(started_at)
then
timestampdiff(
minute,
greatest(started_at,concat(date(started_at),' 09:00:00')),
least(ended_at,concat(date(ended_at),' 22:00:00'))
)
else
timestampdiff(
minute,
least(greatest(started_at,concat(date(started_at),' 09:00:00')),concat(date(started_at),' 22:00:00')),
concat(date(started_at),' 22:00:00')
)
+
timestampdiff(
minute,
concat(date(ended_at),' 09:00:00'),
greatest(least(ended_at,concat(date(ended_at),' 22:00:00')),concat(date(ended_at),' 09:00:00'))
)
+ ((datediff(ended_at,started_at)-1)*780)
end) as total_minutes
from your_table;
--Generating all dates in 2017.
CREATE TABLE CALENDAR AS --Use a different table name if CALENDAR already exists
SELECT '2017-12-31 09:00:00' - INTERVAL c.number DAY AS start_datetime,'2017-12-31 22:00:00' - INTERVAL c.number DAY AS end_datetime
FROM (SELECT singles + tens + hundreds number FROM
(SELECT 0 singles
UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
) singles JOIN
(SELECT 0 tens
UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30
UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60
UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90
) tens JOIN
(SELECT 0 hundreds
UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300
UNION ALL SELECT 400 UNION ALL SELECT 500 UNION ALL SELECT 600
UNION ALL SELECT 700 UNION ALL SELECT 800 UNION ALL SELECT 900
) hundreds
ORDER BY number DESC) c
WHERE c.number BETWEEN 0 and 364
;
--End of table creation
--Actual query begins here
SELECT D.`START`,
D.`END`,
SUM(TIMESTAMPDIFF(MINUTE,GREATEST(D.`START`,C.START_DATETIME), LEAST(D.`END`,C.END_DATETIME))) AS TOTAL_TIME
FROM CALENDAR C
LEFT JOIN DATE_RANGES D ON DATE(C.START_DATETIME) >= DATE(D.`START`)
AND DATE(C.START_DATETIME) <= DATE(D.`END`)
WHERE D.`START` IS NOT NULL
AND D.`END` IS NOT NULL
GROUP BY D.`START`,
D.`END`
;
Construct a calendar table with a dates for a specified number of years. Each date having a start time of 09:00 and an end time of 22:00.
Left join on this table to get one row per date from the date ranges table.
Sum up the differences each day to get the total time worked.
Sample Demo
Day 1 Day 2 Day 3
|--********--|--********--|--********--|
|__________________________|
The question, IMHO is to know how many minutes the first day, and how many minutes the last day, the intermediate days have 780 minutes.
I've used a subquery just to help in the intermediate calculations.
select
if(hour(t1) < 9, date(t1) + interval 9 hour , t1) as tIni1,
date(t1) + interval 22 hour as tFin1,
date(t2) + interval 9 hour as tIni2,
if(hour(t2) > 22, date(t2) + interval 22 hour, t2) as tFin2,
TIMESTAMPDIFF(day, date(t1), date(t2)) numDays
from
tdt
tIni1 and tFin1 is the period of the first day, and tIni2, tFin2 the period of the last day, obviously first and last day can be the same.
Then calculate minutes of first day + minutes of second day + 780 minutes for every intermediate day.
select numDays, tIni1, tFin1, tIni2, tFin2,
if (numDays = 0,
TIMESTAMPDIFF(minute, tIni1, tFin2),
TIMESTAMPDIFF(minute, tIni1, tFin1)
+ TIMESTAMPDIFF(minute, tIni2, tFin2)
+ (numDays - 1) * 780
) as Minutes
from (
select
if(hour(t1) < 9, date(t1) + interval 9 hour , t1) as tIni1,
date(t1) + interval 22 hour as tFin1,
date(t2) + interval 9 hour as tIni2,
if(hour(t2) > 22, date(t2) + interval 22 hour, t2) as tFin2,
TIMESTAMPDIFF(day, date(t1), date(t2)) numDays
from
tdt
) ti
;
Try it here: http://rextester.com/GDHAB78973

Grouping timestamp every 30 minutes

I'm trying to group my timestamp every 30 minutes.
I want my result to be like this:
2016-03-09 00:00:00
2016-03-09 00:30:00
2016-03-09 01:00:00
Instead, my results are this:
2016-03-09 00:00:23
2016-03-09 00:35:02
2016-03-09 01:00:03
The query that I'm using is this
SELECT timestamp
FROM a.tablename
WHERE name = 'example' AND timestamp LIKE '2016-03-09%'
GROUP BY ROUND(((60/30) * HOUR(timestamp) + FLOOR( MINUTE(timestamp) / 30)));
How can I have my desired results? I've researched other answers on SO and non of the answers helped
Here's the basic query to group by 30 minutes interval.
SELECT
FROM_UNIXTIME(ROUND(UNIX_TIMESTAMP(timestamp)/(30* 60)) * (30*60)) thirtyHourInterval
FROM tablename
GROUP BY ROUND(UNIX_TIMESTAMP(timestamp)/(30* 60));
Note: ROUND() may lead you towards wrong output. Use the following query instead. Look at the following example:
SELECT ROUND(3.7), ROUND(4.2);
Result: 4 4.
Both lies in the same segment. The same holds for the above query while rounding the timestamp of different segments might fall in the same segment thus leading towards wrong output
[The following query is Recommended]
SELECT
FROM_UNIXTIME((UNIX_TIMESTAMP(`timestamp`) DIV (30* 60) ) * (30*60)) thirtyHourInterval
FROM tablename
GROUP BY UNIX_TIMESTAMP(`timestamp`) DIV (30* 60)
SQL FIDDLE DEMO
Alternatively you can adopt the following query.
SELECT
FROM_UNIXTIME(ROUND(UNIX_TIMESTAMP(timestamp)/(30* 60)) * (30*60)) thirtyHourInterval
FROM tablename
GROUP BY ( 4 * HOUR( `timestamp` ) + FLOOR( MINUTE( `timestamp` ) / 30 ));
Relatedpost
One method is to use to_seconds(), truncate the value, and then re-create the datetime value:
select date_add(0, interval floor(to_seconds(timestamp) / (30 * 60)) second) as timestamp
from a.tablename
where name = 'example' and timestamp >= '2016-03-09' and timestamp < '2016-03-10'
group by date_add(0, interval floor(to_seconds(timestamp) / (30 * 60)) second)
order by 1;

Group mysql results by "every 30 days"

I have a query:
SELECT
COUNT(id) as amount,
DATEDIFF(expire, buydate) as days
FROM `vw8mv_orders`
GROUP BY MONTH(expire)
The result is:
amount days
1 22
1 30
1 105
1 161
I'd like to see these results in a group (every 30 days). If days value is between 1 and 30 days, then put this in 30days group, if bet 31-60, put to 60days group, etc.
For example:
amount time
2 30 days
0 60 days
1 90 days
You will need to create a calculated column to group by. There are several approaches you could use for the calculation, but a good option might be integer division using the DIV operator:
SELECT
COUNT(id) as amount,
(((datediff(expire, buydate) DIV 30) + 1) * 30) as timegroup
FROM
table
GROUP BY timegroup;
The reason I like this approach, rather than using for example some fancy arithmetic with ROUND(), is that it's a little more clear what you're trying to do. datediff(expire, buydate) DIV 30 says, take the difference of these dates, and tell me "how many 30s" are in that number.
That's all you need for your grouping; the rest is there to make the column display the way you want it, as 30, 60, 90, ... instead of as 0, 1, 2, ....
Another option, if you're not comfortable with integer division, would be the CEILING function:
SELECT
COUNT(id) as amount,
30 * CEILING(datediff(expire, buydate) / 30) as timegroup
FROM
table
GROUP BY timegroup;
Mathematically speaking, CEILING(x / N) is equivalent to ((x DIV N) + 1), but it's a little less busy with CEILING().
You can do a subselect over the result returned from your query,below is the example query
SELECT COUNT(`amount`) as amount,
CONCAT(ROUND(`days` / 30) * 30, ' Days')
as `time`
FROM `t`
GROUP BY `time`
ORDER BY ROUND(`days` / 30)
Demo
For your query you can do so
SELECT COUNT(`amount`) as amount,
CONCAT(ROUND(`days` / 30) * 30, ' Days')
as `time`
FROM(
SELECT COUNT(id) as amount,
datediff(expire, buydate) as days
FROM `vw8mv_orders`
GROUP BY MONTH(expire)
) t
GROUP BY `time`
ORDER BY ROUND(`days` / 30)

Group by half hour interval

I was lucky enough to find this awesome piece of code on Stack Overflow, however I wanted to change it up so it showed each half hour instead of every hour, but messing around with it, only caused me to ruin the query haha.
This is the SQL:
SELECT CONCAT(HOUR(created_at), ':00-', HOUR(created_at)+1, ':00') as hours,
COUNT(*)
FROM urls
GROUP BY HOUR(created_at)
ORDER BY HOUR(created_at) ASC
How would I go about getting a result every half an hour? :)
Another thing, is that, if it there is half an hour with no results, I would like it to return 0 instead of just skipping that step. It looks kinda of weird win I do statistics over the query, when it just skips an hour because there were none :P
If the format isn't too important, you can return two columns for the interval. You might even just need the start of the interval, which can be determined by:
date_format(created_at - interval minute(created_at)%30 minute, '%H:%i') as period_start
the alias can be used in GROUP BY and ORDER BY clauses. If you also need the end of the interval, you will need a small modification:
SELECT
date_format(created_at - interval minute(created_at)%30 minute, '%H:%i') as period_start,
date_format(created_at + interval 30-minute(created_at)%30 minute, '%H:%i') as period_end,
COUNT(*)
FROM urls
GROUP BY period_start
ORDER BY period_start ASC;
Of course you can also concatenate the values:
SELECT concat_ws('-',
date_format(created_at - interval minute(created_at)%30 minute, '%H:%i'),
date_format(created_at + interval 30-minute(created_at)%30 minute, '%H:%i')
) as period,
COUNT(*)
FROM urls
GROUP BY period
ORDER BY period ASC;
Demo: http://rextester.com/RPN50688
Another thing, is that, if it there is half an hour with no results, I
would like it to return 0
If you use the result in a procedural language, you can initialize all 48 rows with zero in a loop and then "inject" the non-zero rows from the result.
However - If you need it to be done in SQL, you will need a table for a LEFT JOIN with at least 48 rows. That could be done inline with a "huge" UNION ALL statement, but (IMHO) it would be ugly. So I prefer to have sequence table with one integer column, which can be very usefull for reports. To create that table I usually use the information_schema.COLUMNS, since it is available on any MySQL server and has at least a couple of hundreds rows. If you need more rows - just join it with itself.
Now let's create that table:
drop table if exists helper_seq;
create table helper_seq (seq smallint auto_increment primary key)
select null
from information_schema.COLUMNS c1
, information_schema.COLUMNS c2
limit 100; -- adjust as needed
Now we have a table with integers from 1 to 100 (though right now you only need 48 - but this is for demonstration).
Using that table we can now create all 48 time intervals:
select time(0) + interval 30*(seq-1) minute as period_start,
time(0) + interval 30*(seq) minute as period_end
from helper_seq s
where s.seq <= 48;
We will get the following result:
period_start | period_end
00:00:00 | 00:30:00
00:30:00 | 01:00:00
...
23:30:00 | 24:00:00
Demo: http://rextester.com/ISQSU31450
Now we can use it as a derived table (subquery in FROM clause) and LEFT JOIN your urls table:
select p.period_start, p.period_end, count(u.created_at) as cnt
from (
select time(0) + interval 30*(seq-1) minute as period_start,
time(0) + interval 30*(seq) minute as period_end
from helper_seq s
where s.seq <= 48
) p
left join urls u
on time(u.created_at) >= p.period_start
and time(u.created_at) < p.period_end
group by p.period_start, p.period_end
order by p.period_start
Demo: http://rextester.com/IQYQ32927
Last step (if really needed) is to format the result. We can use CONCAT or CONCAT_WS and TIME_FORMAT in the outer select. The final query would be:
select concat_ws('-',
time_format(p.period_start, '%H:%i'),
time_format(p.period_end, '%H:%i')
) as period,
count(u.created_at) as cnt
from (
select time(0) + interval 30*(seq-1) minute as period_start,
time(0) + interval 30*(seq) minute as period_end
from helper_seq s
where s.seq <= 48
) p
left join urls u
on time(u.created_at) >= p.period_start
and time(u.created_at) < p.period_end
group by p.period_start, p.period_end
order by p.period_start
The result would look like:
period | cnt
00:00-00:30 | 1
00:30-01:00 | 0
...
23:30-24:00 | 3
Demo: http://rextester.com/LLZ41445
Switch to seconds.
Do arithmetic to get a number for each unit of time (using 30*60 for half-hour, in your case)
Have a table of consecutive numbers.
Use LEFT JOIN to get even missing units of time.
Do the GROUP BY.
Convert back from units of time to actual time -- for display.
(Steps 3 and 4 are optional. The question says "every", so I assume they are needed.)
Steps 1 and 2 are embodied in something like
FLOOR(UNIX_TIMESTAMP(created_at) / (30*60))
For example:
mysql> SELECT NOW(), FLOOR(UNIX_TIMESTAMP(NOW()) / (30*60));
+---------------------+----------------------------------------+
| NOW() | FLOOR(UNIX_TIMESTAMP(NOW()) / (30*60)) |
+---------------------+----------------------------------------+
| 2018-03-02 08:24:48 | 844448 |
+---------------------+----------------------------------------+
Step 3 is needs to be done once and kept in a permanent table. Or, if you have MariaDB, use a "seq" pseudo-table; for example `seq_844448_to_900000 would dynamically give a table that would reach pretty far into the future.
Step 6 example:
mysql> SELECT DATE_FORMAT(FROM_UNIXTIME((844448) * 30*60), "%b %d %h:%i");
+-------------------------------------------------------------+
| DATE_FORMAT(FROM_UNIXTIME((844448) * 30*60), "%b %d %h:%i") |
+-------------------------------------------------------------+
| Mar 02 08:00 |
+-------------------------------------------------------------+
+---------------------------------------------------------------+
| DATE_FORMAT(FROM_UNIXTIME((844448+1) * 30*60), "%b %d %h:%i") |
+---------------------------------------------------------------+
| Mar 02 08:30 |
+---------------------------------------------------------------+
Well, this could be a bit verbose but it works:
SELECT hours, SUM(count) as count FROM (
SELECT CONCAT(HOUR(created_at), ':', LPAD(30 * FLOOR(MINUTE(created_at)/30), 2, '0'), '-',
HOUR(DATE_ADD(created_at, INTERVAL 30 minute)), ':', LPAD(30 * FLOOR(MINUTE(DATE_ADD(created_at, INTERVAL 30 minute))/30), 2, '0')) as hours,
COUNT(*) as count
FROM urls
GROUP BY HOUR(created_at), FLOOR(MINUTE(created_at)/30)
UNION ALL
SELECT '00:00-00:30'as hours, 0 as count UNION ALL SELECT '00:30-01:00'as hours, 0 as count UNION ALL
SELECT '01:00-01:30'as hours, 0 as count UNION ALL SELECT '01:30-02:00'as hours, 0 as count UNION ALL
SELECT '02:00-02:30'as hours, 0 as count UNION ALL SELECT '02:30-03:00'as hours, 0 as count UNION ALL
SELECT '03:00-03:30'as hours, 0 as count UNION ALL SELECT '03:30-04:00'as hours, 0 as count UNION ALL
SELECT '04:00-04:30'as hours, 0 as count UNION ALL SELECT '04:30-05:00'as hours, 0 as count UNION ALL
SELECT '05:00-05:30'as hours, 0 as count UNION ALL SELECT '05:30-06:00'as hours, 0 as count UNION ALL
SELECT '06:00-06:30'as hours, 0 as count UNION ALL SELECT '06:30-07:00'as hours, 0 as count UNION ALL
SELECT '07:00-07:30'as hours, 0 as count UNION ALL SELECT '07:30-08:00'as hours, 0 as count UNION ALL
SELECT '08:00-08:30'as hours, 0 as count UNION ALL SELECT '08:30-09:00'as hours, 0 as count UNION ALL
SELECT '09:00-09:30'as hours, 0 as count UNION ALL SELECT '09:30-10:00'as hours, 0 as count UNION ALL
SELECT '10:00-10:30'as hours, 0 as count UNION ALL SELECT '10:30-11:00'as hours, 0 as count UNION ALL
SELECT '11:00-11:30'as hours, 0 as count UNION ALL SELECT '11:30-12:00'as hours, 0 as count UNION ALL
SELECT '12:00-12:30'as hours, 0 as count UNION ALL SELECT '12:30-13:00'as hours, 0 as count UNION ALL
SELECT '13:00-13:30'as hours, 0 as count UNION ALL SELECT '13:30-14:00'as hours, 0 as count UNION ALL
SELECT '14:00-14:30'as hours, 0 as count UNION ALL SELECT '14:30-15:00'as hours, 0 as count UNION ALL
SELECT '15:00-15:30'as hours, 0 as count UNION ALL SELECT '15:30-16:00'as hours, 0 as count UNION ALL
SELECT '16:00-16:30'as hours, 0 as count UNION ALL SELECT '16:30-17:00'as hours, 0 as count UNION ALL
SELECT '17:00-17:30'as hours, 0 as count UNION ALL SELECT '17:30-18:00'as hours, 0 as count UNION ALL
SELECT '18:00-18:30'as hours, 0 as count UNION ALL SELECT '18:30-19:00'as hours, 0 as count UNION ALL
SELECT '19:00-19:30'as hours, 0 as count UNION ALL SELECT '19:30-20:00'as hours, 0 as count UNION ALL
SELECT '20:00-20:30'as hours, 0 as count UNION ALL SELECT '20:30-21:00'as hours, 0 as count UNION ALL
SELECT '21:00-21:30'as hours, 0 as count UNION ALL SELECT '21:30-22:00'as hours, 0 as count UNION ALL
SELECT '22:00-22:30'as hours, 0 as count UNION ALL SELECT '22:30-23:00'as hours, 0 as count UNION ALL
SELECT '23:00-23:30'as hours, 0 as count UNION ALL SELECT '23:30-00:00'as hours, 0 as count
) AS T
GROUP BY hours ORDER BY hours;
The most difficult part of your query is output of statistics for intervals that don't have any hits. SQL is all about querying and aggregating existing data; selecting or aggregating the data missing in the table is quite unordinary task. That's why, like Wolph stated in comments, there is no pretty solution for this task.
I solved this problem by explicitly selecting all half intervals of the day. This solution could be used if number of intervals is limited like in your case. This will not work however if you aggregate by different days from long period of time.
I'm not a fan of this query but I can't propose anything better. More elegant solution could be achieved with stored procedure with a loop, but seems like you want to solve it with raw SQL query.
You can add some math to calculate 48 intervals instead of 24 and put it into another field by which you're going to group and sort.
SELECT HOUR(created_at)*2+FLOOR(MINUTE(created_at)/30) as interval48,
if(HOUR(created_at)*2+FLOOR(MINUTE(created_at)/30) % 2 =0,
CONCAT(HOUR(created_at), ':00-', HOUR(created_at), ':30'),
CONCAT(HOUR(created_at), ':30-', HOUR(created_at)+1, ':00')
) as hours,
count(*)
FROM urls
GROUP BY HOUR(created_at)*2+FLOOR(MINUTE(created_at)/30)
ORDER BY HOUR(created_at)*2+FLOOR(MINUTE(created_at)/30) ASC
Example of result:
0 0:00-0:30 2017
1 0:30-1:00 1959
2 1:30-2:00 1830
3 1:30-2:00 1715
4 2:30-3:00 1679
5 2:30-3:00 1688
The result of original query posted by Jazerix was:
0:00-1:00 3976
1:00-2:00 3545
2:00-3:00 3367
A different Approach without creating additional tables. May look like a hack though :-)
Step 1 : Generate a Time Table Dynamically
Assumption : INFORMATION_SCHEMA DB is avaialble and has a table COLLATIONS which normally has more than 100 records. You can use any table which has minimum 48 records
Query :
SELECT #time fromTime, ADDTIME(#time, '00:29:00') toTime,
#time := ADDTIME(#time, '00:30:00')
FROM information_schema.COLLATIONS
JOIN (SELECT #time := TIME('00:00:00')) a
WHERE #time < '24:00:00'
Above query will give a table with from time and to time with an interval of 30 minutes.
Step 2 : Use the first query to generate required result joining urls table
Query :
SELECT CONCAT(fromTime, '-', toTime) AS halfHours, COUNT(created_at)
FROM
(SELECT #time fromTime, ADDTIME(#time, '00:29:00') toTime, #time := ADDTIME(#time, '00:30:00')
FROM information_schema.COLLATIONS
JOIN (SELECT #time := TIME('00:00:00')) a
WHERE #time < '24:00:00'
) timeTable
LEFT JOIN urls ON HOUR(created_at) BETWEEN HOUR(fromTime) AND HOUR(toTime)
AND MINUTE(created_at) BETWEEN MINUTE(fromTime) AND MINUTE(toTime)
GROUP BY fromTime
SQLFiddle
I hope this will work for,
SELECT
#sTime:= CONCAT(HOUR(created_at),":",
(CASE WHEN MINUTE(created_at) > 30 THEN 30 ELSE 0 END)) as intVar,
(CONCAT(
AddTime(#sTime, '00:00:00'),
' to ',
AddTime(#sTime, '00:30:00')
)) as timeInterval,
COUNT(*) FROM urls
GROUP BY
(CONCAT(HOUR(created_at),":",(CASE WHEN MINUTE(created_at) > 30 THEN 30 ELSE 0 END)))
ORDER BY HOUR(created_at) ASC
Simply convert to sec and divide by 30 mins(1800secs). And to verify i used min, max on timestamp.
SELECT concat(TIME_FORMAT(min(created_at),"%H:%i")," - ", TIME_FORMAT(max(created_at),"%H:%i")) as hours,
COUNT(*)
FROM urls
GROUP BY FLOOR(TIME_TO_SEC(created_at)/1800)
ORDER BY HOUR(created_at) ASC