I have a datetime column and I can easily run queries with a group by on my datetime column. However, I would like to run group queries for
- 1-minute intervals
- 2-minute intervals
- 5-minute intervals
- 1 hour intervals
- etc.
How do I do this?
I have a datetime column and I can easily run queries with a group by on my datetime column. However, I would like to run group queries for
How do I do this?
First, a table and some sample data to play with:
USE tempdb;
GO
CREATE TABLE dbo.SomeTable(dt DATETIME);
GO
SET NOCOUNT ON;
GO
INSERT dbo.SomeTable(dt) SELECT DATEADD(MINUTE, -22, GETDATE());
GO 45
INSERT dbo.SomeTable(dt) SELECT DATEADD(MINUTE, -19, GETDATE());
GO 32
INSERT dbo.SomeTable(dt) SELECT DATEADD(MINUTE, -17, GETDATE());
GO 21
INSERT dbo.SomeTable(dt) SELECT DATEADD(MINUTE, -12, GETDATE());
GO 16
INSERT dbo.SomeTable(dt) SELECT DATEADD(MINUTE, -5, GETDATE());
GO 55
INSERT dbo.SomeTable(dt) SELECT DATEADD(MINUTE, -2, GETDATE());
GO 26
INSERT dbo.SomeTable(dt) SELECT DATEADD(MINUTE, -1, GETDATE());
GO 71
INSERT dbo.SomeTable(dt) SELECT GETDATE();
GO 14
(I'd do this in sqlfiddle but I'm not sure it supports GO <int>
to have many rows, and it chokes on INSERT
> 8000 characters.)
Now a stored procedure:
CREATE PROCEDURE dbo.GetGroupedIntervals
@MinuteInterval TINYINT = 1
AS
BEGIN
SET NOCOUNT ON;
DECLARE
@IntervalCount INT, @StartDate SMALLDATETIME;
SELECT
@StartDate = DATEADD(MINUTE, -1, MIN(dt)),
@IntervalCount = (DATEDIFF(MINUTE, MIN(dt), MAX(dt))
+ @MinuteInterval) / @MinuteInterval
FROM dbo.SomeTable -- WHERE ...;
;WITH dates(s,e) AS
(
SELECT
DATEADD(MINUTE, @MinuteInterval*(n.n-1), @StartDate),
DATEADD(MINUTE, @MinuteInterval*(n.n), @StartDate)
FROM
(
SELECT
TOP (@IntervalCount) ROW_NUMBER() OVER (ORDER BY o.[object_id])
FROM sys.all_objects AS o CROSS JOIN sys.all_columns AS c
ORDER BY o.[object_id]
) AS n(n)
)
SELECT StartDate = d.s, c = COUNT(s.dt)
FROM dates AS d
LEFT OUTER JOIN dbo.SomeTable AS s
ON s.dt >= d.s AND s.dt < d.e
-- AND any filter criteria for dbo.SomeTable?
GROUP BY d.s
ORDER BY d.s;
END
GO
And some sample usage:
EXEC dbo.GetGroupedIntervals @MinuteInterval = 1;
EXEC dbo.GetGroupedIntervals @MinuteInterval = 2;
EXEC dbo.GetGroupedIntervals @MinuteInterval = 5;
For brevity, I'll show the results for the last call, but you can play with the others.
StartDate c
------------------- ----
2012-05-16 12:51:00 77
2012-05-16 12:56:00 21
2012-05-16 13:01:00 16
2012-05-16 13:06:00 55
2012-05-16 13:11:00 111
Some notes:
dates
to produce all the intervals for the day, rather than all the intervals between the min and max time found in dbo.SomeTable
on that day. The following variation takes care of this, by presenting the data for a single day starting from midnight and incrementing by @MinutInterval:...
CREATE PROCEDURE dbo.GetGroupedIntervalsByDay
@Date DATE,
@MinuteInterval TINYINT = 1
AS
BEGIN
SET NOCOUNT ON;
DECLARE
@IntervalCount INT,
@StartDate SMALLDATETIME = @Date;
SELECT
@IntervalCount = 1440 / @MinuteInterval;
;WITH dates(s,e) AS
(
SELECT
DATEADD(MINUTE, @MinuteInterval*(n.n-1), @StartDate),
DATEADD(MINUTE, @MinuteInterval*(n.n), @StartDate)
FROM
(
SELECT
TOP (@IntervalCount) ROW_NUMBER() OVER (ORDER BY o.[object_id])
FROM sys.all_columns AS o
ORDER BY o.[object_id]
) AS n(n)
)
SELECT StartDate = d.s, c = COUNT(s.dt)
FROM dates AS d
LEFT OUTER JOIN dbo.SomeTable AS s
ON s.dt >= d.s AND s.dt < d.e
-- AND any filter criteria for dbo.SomeTable?
GROUP BY d.s
ORDER BY d.s;
END
GO
Sample calls:
EXEC dbo.GetGroupedIntervalsByDay @Date = '20120516', @MinuteInterval = 1;
EXEC dbo.GetGroupedIntervalsByDay @Date = '20120516', @MinuteInterval = 2;
EXEC dbo.GetGroupedIntervalsByDay @Date = '20120516', @MinuteInterval = 5;
Abridged results from the last call:
StartDate c
------------------- ----
2012-05-16 00:00:00 0
2012-05-16 00:05:00 0
2012-05-16 00:10:00 0
...
2012-05-16 12:40:00 0
2012-05-16 12:45:00 0
2012-05-16 12:50:00 45
2012-05-16 12:55:00 53
2012-05-16 13:00:00 16
2012-05-16 13:05:00 55
2012-05-16 13:10:00 111
2012-05-16 13:15:00 0
2012-05-16 13:20:00 0
...
2012-05-16 23:45:00 0
2012-05-16 23:50:00 0
2012-05-16 23:55:00 0
(Again if you don't want to include the intervals with no counts, change the left outer join to an inner join. You also might find some odd results if you choose an interval that doesn't fit nicely inside of 1440. I'll leave that case as an exercise to the reader.)
declare @T table
(
Value datetime
);
insert into @T values ('2012-01-01T00:02:00');
insert into @T values ('2012-01-01T00:03:00');
insert into @T values ('2012-01-01T00:04:00');
insert into @T values ('2012-01-01T00:05:00');
insert into @T values ('2012-01-01T00:06:00');
insert into @T values ('2012-01-01T00:07:00');
insert into @T values ('2012-01-01T00:08:00');
insert into @T values ('2012-01-01T00:09:00');
insert into @T values ('2012-01-01T00:10:00');
insert into @T values ('2012-01-01T00:11:00');
-- Interval in minutes
declare @Interval int
set @Interval = 4
select count(*) as "Count",
dateadd(minute, (datediff(minute, 0, Value) / @Interval) * @Interval, 0) as "Value"
from @T
group by dateadd(minute, (datediff(minute, 0, Value) / @Interval) * @Interval, 0);
Result:
Count Value
----------- -----------------------
2 2012-01-01 00:00:00.000
4 2012-01-01 00:04:00.000
4 2012-01-01 00:08:00.000
Since DATEPART
returns integers...
SELECT
(DATEPART(MINUTE, StartDate) / 5) * 5, COUNT(*)
FROM
Table
GROUP BY
(DATEPART(MINUTE, StartDate) / 5) * 5
ORDER BY
(DATEPART(MINUTE, StartDate) / 5) * 5
The numbers shown are the start of the five-minute period. So the row labeled "30" covers events happening at X:30, X:31, X:32, X:33, and X:34.
4:30
will be combined with 5:30
and 6:30
since the minute is the same across the board. You need to include date and hour as well at a minimum.