Related
I'm trying to create a histogram based on interval of dates and total number of orders but im having a hard time binning it through SQL.
A simplified table can be seen below
customer_id
Date
count_orders
1
01-01-2020
5
1
01-13-2020
26
1
02-06-2020
11
2
01-17-2020
9
3
02-04-2020
13
3
03-29-2020
24
4
04-05-2020
1
5
02-23-2020
10
6
03-15-2020
7
6
04-18-2020
32
...
...
...
and im thinking of binning it into 20 day intervals but the only thing I can think about is do a
SUM(CASE WHEN Date BETWEEN <interval1_startdate> AND <interval1_enddate> ...)
method per interval which if used into the actual data (which contains millions of row) is quite exhausting. So i need help in automating the binning part.
Desired output would either be
1)
interval
total_count
01-01-2020 - 01-20-2020
31
01-21-2020 - 02-10-2020
24
02-10-2020 - 03-01-2020
10
...
...
or 2)
start
end
total_count
01-01-2020
01-20-2020
31
01-21-2020
02-10-2020
24
02-10-2020
03-01-2020
10
...
...
...
Do you have any ideas?
You can group by the (current date - minimum date)/20. For preso something like this:
WITH dataset (customer_id, Date, count_orders) AS (
VALUES (1, date_parse('01-01-2020', '%m-%d-%Y'), 5),
(1, date_parse('01-13-2020', '%m-%d-%Y'), 26),
(1, date_parse('02-06-2020', '%m-%d-%Y'), 11),
(2, date_parse('01-17-2020', '%m-%d-%Y'), 9),
(3, date_parse('02-04-2020', '%m-%d-%Y'), 13),
(3, date_parse('03-29-2020', '%m-%d-%Y'), 24),
(4, date_parse('04-05-2020', '%m-%d-%Y'), 1),
(5, date_parse('02-23-2020', '%m-%d-%Y'), 10),
(6, date_parse('03-15-2020', '%m-%d-%Y'), 7),
(6, date_parse('04-18-2020', '%m-%d-%Y'), 32)
)
SELECT date_add('day', 20 * grp, min(min_date)) interval_end,
date_add('day', 20 * (grp + 1) - 1, min(min_date)) interval_end,
sum(count_orders) total_count
FROM (
SELECT *,
date_diff('day', min(date) over (), date) / 20 as grp,
min(date) over () min_date
FROM dataset
)
group by grp
order by 1
Output:
interval_end
interval_end
total_count
2020-01-01 00:00:00.000
2020-01-20 00:00:00.000
40
2020-01-21 00:00:00.000
2020-02-09 00:00:00.000
24
2020-02-10 00:00:00.000
2020-02-29 00:00:00.000
10
2020-03-01 00:00:00.000
2020-03-20 00:00:00.000
7
2020-03-21 00:00:00.000
2020-04-09 00:00:00.000
25
2020-04-10 00:00:00.000
2020-04-29 00:00:00.000
32
You can get the intervals using CTE and then get the total using cross apply.
Drop table Tbl
Create Table Tbl (customer_id Int, [date] Date, count_orders Int)
Insert Into Tbl (customer_id, [date], count_orders)
Values (1,'2020-01-01', 5),
(1,'2020-01-13',26),
(1,'2020-02-06',11),
(2,'2020-01-17',9),
(3,'2020-02-04',13),
(3,'2020-03-29',24),
(4,'2020-04-05',1),
(5,'2020-02-23',10),
(6,'2020-03-15',7),
(6,'2020-04-18',32)
;With A As (
Select Min([date]) As start, DateAdd(dd,19,Min([date])) As [end], Max([date]) As [max]
From Tbl
Union All
Select DateAdd(dd,1,[end]) As start, DateAdd(dd,20,[end]) As [end], [max]
From A
Where [end]<[max])
Select A.[start], A.[end], T.total_count
From A Cross Apply (Select SUM(count_orders) As total_count
From Tbl Where [date] between A.[start] And A.[end]) As T
Result:
start end total_count
---------- ---------- -----------
2020-01-01 2020-01-20 40
2020-01-21 2020-02-09 24
2020-02-10 2020-02-29 10
2020-03-01 2020-03-20 7
2020-03-21 2020-04-09 25
2020-04-10 2020-04-29 32
sql fiddle example
I have this table structure :
CREATE TABLE TIMETABLE
([ID] int, [Name] varchar(50), [StartDate] datetime, [EndDate] datetime)
;
INSERT INTO TIMETABLE
([ID], [Name], [StartDate], [EndDate])
VALUES
(1, 'John', '2017-01-29 16:00:00.000', '2017-01-29 16:12:00.000'),
(2, 'Mario', '2017-01-29 16:17:00.000', '2017-01-29 16:29:00.000'),
(3, 'Kate', '2017-01-15 10:35:00.000', '2017-01-15 10:40:00.000'),
(4, 'Maria', '2017-01-15 10:17:00.000', '2017-01-15 10:27:00.000'),
(5, 'Oliver', '2017-01-15 13:46:00.000', '2017-01-29 14:00:00.000')
;
And The result for this :
select * from TIMETABLE
ID Name StartDate EndDate
1 John 2017-01-29T16:00:00Z 2017-01-29T16:12:00Z
2 Mario 2017-01-29T16:17:00Z 2017-01-29T16:29:00Z
3 Kate 2017-01-15T10:35:00Z 2017-01-15T10:40:00Z
4 Maria 2017-01-15T10:17:00Z 2017-01-15T10:27:00Z
5 Oliver 2017-01-15T13:46:00Z 2017-01-29T14:00:00Z
I want to know with a range from 15 mins in wich range is the date, for example:
ID Name StartDate EndDate HourRangeTime
1 John 2017-01-29T16:00:00Z 2017-01-29T16:12:00Z 16:00
In the example the startdate and the enddate is in the range between 16:00 and 16:12 is in the range 16:00
The result it should be like this:
ID Name StartDate EndDate HourRangeTime
1 John 2017-01-29T16:00:00Z 2017-01-29T16:12:00Z 16:00
2 Mario 2017-01-29T16:17:00Z 2017-01-29T16:29:00Z 16:15
3 Kate 2017-01-15T10:35:00Z 2017-01-15T10:40:00Z 10:30
4 Maria 2017-01-15T10:17:00Z 2017-01-15T10:27:00Z 10:15
5 Oliver 2017-01-15T13:46:00Z 2017-01-29T14:00:00Z 13:45
How can I fill the column HourRangeTime, take dates and see what range does it belong to?
Your seem focused on the StartDate.
A relatively general way to do this is to convert this to minutes and then truncate the minutes to the nearest 15 minutes. Here is code:
select cast(dateadd(minute,
15 * (datediff(minute, 0,
cast(StartDate as time)
) / 15
), 0
) as time)
This returns the result as a time.
You can get difference and process future.
SELECT StartTime, EndTime, DATEDIFF(MINUTE, StartTime , EndTime) AS MinuteDiff
FROM TIMETABLE
You can try this for your desired output:
SELECT
CONCAT(DATEPART(hh,StartDate), ':',
CASE
WHEN DATEPART(MINUTE,StartDate) BETWEEN 0 AND 14 THEN '00'
WHEN DATEPART(MINUTE,StartDate) BETWEEN 15 AND 29 THEN '15'
WHEN DATEPART(MINUTE,StartDate) BETWEEN 30 AND 44 THEN '30'
WHEN DATEPART(MINUTE,StartDate) BETWEEN 45 AND 59 THEN '45'
ELSE '00'
END) AS HourRangeTime
FROM TIMETABLE
OUTPUT:
HourRangeTime
-------------
16:00
16:15
10:30
10:15
13:45
You can use this.
SELECT *,
CONVERT(VARCHAR,DATEPART(HOUR, [StartDate]))
+ ':'
+ RIGHT(CONVERT(VARCHAR,(DATEPART(MINUTE, [StartDate]) / 15) * 15)+'0',2) HourRangeTime FROM TIMETABLE
Hoping someone can assist with how to modify the following SQL to achieve the result shown in desired output. I am not fluent in TSQL but know enough to get this far.
My objectrive is to count my incident data and group into 15 minutes time segments starting at midnight but also include zero (0) where there is no incident data in a particular time segment.
Curent Query#
;With cte As
(SELECT CONVERT (varchar(5),DATEADD(minute, 15 *
(DATEDIFF(minute, '20000101', I.CreateTimestamp) / 15), '20000101'),108)
AS CreationTime, I.IncidentShortReference AS Ref
FROM Incident I
WHERE i.CreateTimestamp between DATEADD(d,-1,GETDATE()) and GETDATE()
)
SELECT CTE.CreationTime, count(CTE.Ref) As Count
FROM cte CTE
GROUP BY CTE.CreationTime
ORDER BY CTE.CreationTime
My result
CreationTime count
00:15 2
01:00 1
01:15 1
01:30 1
01:45 2
02:00 1
02:15 1
02:30 4
(Truncated)
Desired Output
CreationTime count
00:15 2
00:30 0
00:45 0
01:00 1
01:15 1
01:30 1
01:45 2
02:00 1
02:15 1
02:30 4
02:45 0
03:00 0
(Truncated)
This uses a cte creating a record for every timestamp between midnight yesterday and now, with a count for the number of incidents in each range with sample data:
declare #incident table (CreateTimestamp datetime, IncidentShortReference varchar(5))
insert into #incident values ('4/10/2017 11:11:00', 'test')
insert into #incident values ('4/10/2017 11:12:00', 'test')
insert into #incident values ('4/10/2017 11:21:00', 'test')
insert into #incident values ('4/10/2017 11:31:00', 'test')
insert into #incident values ('4/10/2017 13:31:00', 'test')
DECLARE #dt datetime
SELECT #dt = dateadd(d, datediff(d, 0, getdate()), 0) - 1 -- yesterday at midnight
;with cte as
(
select #dt dt
union all
select DATEADD(minute, 15, dt) as Next15
FROM cte
WHERE DATEADD(minute, 15, dt) < GETDATE()
)
select convert(varchar(5), dt, 108) as CreationTime, (select count(*) FROM #incident WHERE CreateTimestamp >= dt and CreateTimestamp < dateadd(mi, 15, dt)) as count
from cte
Sample output from a random interval:
You could create a time interval CTE table like this
WITH TIME_CTE
AS(
SELECT
CAST('20170411 00:15:00' AS DATETIME) AS TimePeriod
UNION ALL
SELECT
DATEADD(MINUTE, 15, TimePeriod)
FROM TIME_CTE
WHERE
DATEADD(MINUTE, 15, TimePeriod) < CAST('20170411 23:59:00' AS DATETIME)
)
SELECT
LEFT(CONVERT(VARCHAR(10),TimePeriod,108), 5)
FROM TIME_CTE
Then join it with your original query
WITH TIME_CTE
AS(
SELECT
CAST('20170411 00:15:00' AS DATETIME) AS TimePeriod
UNION ALL
SELECT
DATEADD(MINUTE, 15, TimePeriod)
FROM TIME_CTE
WHERE
DATEADD(MINUTE, 15, TimePeriod) < CAST('20170411 23:59:00' AS DATETIME)
),
CTE
AS (
SELECT CONVERT (varchar(5),DATEADD(minute, 15 *
(DATEDIFF(minute, '20000101', I.CreateTimestamp) / 15), '20000101'),108)
AS CreationTime, I.IncidentShortReference AS Ref
FROM Incident I
WHERE i.CreateTimestamp between DATEADD(d,-1,GETDATE()) and GETDATE()
)
SELECT TIME_CTE.TimePeriod, SUM(IIF(CTE.Ref IS NULL, 0, 1)) As Count
FROM TIME_CTE
LEFT JOIN CTE ON CTE.CreationTime = TIME_CTE.TimePeriod
GROUP BY TIME_CTE.TimePeriod
ORDER BY TIME_CTE.TimePeriod
I have following table:
Date Reading1 Reading2
2017-02-15 07:00:00.0000000 33 30
2017-02-15 07:15:00.0000000 32 31
2017-02-15 07:30:00.0000000 32 31
2017-02-15 07:45:00.0000000 33 30
2017-02-15 08:00:00.0000000 33 28
2017-02-15 08:15:00.0000000 32 29
2017-02-15 08:30:00.0000000 32 31
2017-02-15 08:45:00.0000000 34 31
2017-02-15 09:00:00.0000000 34 31
2017-02-15 09:15:00.0000000 34 30
2017-02-15 09:30:00.0000000 31 30
2017-02-15 09:45:00.0000000 32 32
........
2017-02-16 06:15:00.0000000 32 31
2017-02-16 06:30:00.0000000 35 32
2017-02-16 06:45:00.0000000 34 30
2017-02-16 07:00:00.0000000 34 31
I can sum the Reading1 and Reading2 column based on hour or date, but my problem is that I want to sum-up the column between 7AM to 7PM and then 7PM to 7AM of the next day. Any help will be highly appreciable.
'For sum on hour I am using following query'
--Sum on hour
select datepart(hour,Date), SUM(Reading1), SUM(Reading2)
from #LocalTempTable
group by
datepart(hour,Date),
dateadd(d, 0, datediff(d, 0,Date))
For any given day, I would approach this with a couple of BETWEEN subqueries. Something like:
declare #refDate datetime
declare #midPeriod datetime
declare #endPeriod datetime
set #refDate = '2017-02-15 07:00'
set #midPeriod = dateadd(hh, 12, #refDate)
set #endPeriod = dateadd(hh, 24, #refDate)
select #refDate PeriodStart,
(select sum(Reading1) from #LocalTempTable where Date between #refDate and #midPeriod) EarlyReading1,
(select sum(Reading1) from #LocalTempTable where Date between #midPeriod and #endPeriod) LateReading1,
(select sum(Reading2) from #LocalTempTable where Date between #refDate and #midPeriod) EarlyReading2,
(select sum(Reading2) from #LocalTempTable where Date between #midPeriod and #endPeriod) LateReading2
You can use a case statement to group on the hours to get the desired results:
select year(Date) as DateYear,
datepart(dy, Date) as DayOfYear,
case when datepart(hour,Date) >= 7 and datepart(hour, Date) < 19 then '7AM - 7PM' else '7PM - 7AM' end as HourGroup,
SUM(Reading1),
SUM(Reading2)
from #LocalTempTable
group by year(Date) as DateYear,
datepart(dy, Date) as DayOfYear,
case when datepart(hour,Date) >= 7 and datepart(hour, Date) < 19 then '7AM - 7PM' else '7PM - 7AM' end
Here's a full script with some sample data:
CREATE TABLE #Readings ([Date] DateTime, Reading1 int, Reading2 int)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-15 06:45:00', 1, 1)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-15 07:00:00', 2, 2)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-15 07:15:00', 3, 3)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-15 18:45:00', 4, 4)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-15 19:15:00', 5, 5)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-16 06:45:00', 6, 6)
INSERT INTO #Readings ([Date], Reading1, Reading2) VALUES ('2017-02-16 07:00:00', 7, 7)
SELECT
DATEADD(hh, (Half * 12) + 7, ModifiedDay) AS StartPeriod,
SUM(Reading1) AS SumOfReading1,
SUM(Reading2) AS SumOfReading2
FROM (
SELECT
[Date],
CAST(CAST(ModifiedDate AS DATE) AS DATETIME) AS [ModifiedDay],
DATEPART(hh, ModifiedDate) AS [DatePart],
CASE WHEN DATEPART(hh, ModifiedDate) < 12 THEN 0 ELSE 1 END AS Half,
Reading1,
Reading2
FROM (
SELECT
[Date],
DATEADD(hh, -7, [Date]) AS [ModifiedDate],
Reading1,
Reading2
FROM #Readings
) t
) t
GROUP BY DATEADD(hh, (Half * 12) + 7, ModifiedDay), Half
ORDER BY 1, 2
DROP TABLE #Readings
StartPeriod SumOfReading1 SumOfReading2
2017-02-14 19:00:00.000 1 1
2017-02-15 07:00:00.000 9 9
2017-02-15 19:00:00.000 11 11
2017-02-16 07:00:00.000 7 7
Without using a calendar table or cte:
test setup: http://rextester.com/ZENTG4450
select
FromDate = convert(varchar(10)
,min(dateadd(day,(datediff(hour,0,date)-7)/24,0))
,120)
, ThruDate = convert(varchar(10)
,max(dateadd(day,(datediff(hour,0,date)+5)/24,0))
,120)
, Hours = case ((datediff(hour,0,date)+5)/12)%2
when 1
then '7 AM to 7 PM'
else '7 PM to 7 AM'
end
, SumReading1=sum(Reading1)
, SumReading2=sum(Reading2)
from t
group by (datediff(hour,0,date)+5)/12
returns:
+------------+------------+--------------+-------------+-------------+
| FromDate | ThruDate | Hours | SumReading1 | SumReading2 |
+------------+------------+--------------+-------------+-------------+
| 2017-02-15 | 2017-02-15 | 7 AM to 7 PM | 392 | 364 |
| 2017-02-15 | 2017-02-16 | 7 PM to 7 AM | 101 | 93 |
| 2017-02-16 | 2017-02-16 | 7 AM to 7 PM | 34 | 31 |
+------------+------------+--------------+-------------+-------------+
using a calendar table or cte:
test setup: http://rextester.com/QOC88855
declare #fromdate date = '20170201'
declare #thrudate date = '20170228'
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top ((datediff(day, #fromdate, #thrudate)+1)*2)
[FromDate]=dateadd(hour,7+12*((row_number() over (order by (select 1)) -1)%2)
,convert(datetime2(2)
,dateadd(day, (row_number() over (order by (select 1)) -1)/2, #fromdate))
)
, [ThruDate]=dateadd(hour,19+12*((row_number() over (order by (select 1)) -1)%2)
,convert(datetime2(2)
,dateadd(day, (row_number() over (order by (select 1)) -1)/2, #fromdate))
)
from n as deka
cross join n as hecto /* 100 days */
--cross join n as kilo /* 2.73 years */
--cross join n as [tenK] /* 27.3 years */
order by 1
)
select
FromDate=convert(varchar(20),FromDate,120)
, ThruDate=convert(varchar(20),ThruDate,120)
, SumReading1=sum(Reading1)
, SumReading2=sum(Reading2)
from dates d
inner join t
on t.date >= d.fromdate
and t.date < d.thrudate
group by d.FromDate, d.ThruDate
order by d.FromDate, d.ThruDate
returns:
+---------------------+---------------------+-------------+-------------+
| FromDate | ThruDate | SumReading1 | SumReading2 |
+---------------------+---------------------+-------------+-------------+
| 2017-02-15 07:00:00 | 2017-02-15 19:00:00 | 392 | 364 |
| 2017-02-15 19:00:00 | 2017-02-16 07:00:00 | 101 | 93 |
| 2017-02-16 07:00:00 | 2017-02-16 19:00:00 | 34 | 31 |
+---------------------+---------------------+-------------+-------------+
Assuming your [Date] column is a DATETIME column, you can do this:(Basically what it does is to group the time range from 7AM-7PM as one and 7PM-7AM as another.
select FORMAT(dateadd(hour,-7,[date]), 'yyyy-MM-dd') + case when DATEPART(hour,dateadd(hour,-7,[date])) between 0 and 11 then ' 7AM-7PM' ELSE ' 7PM-7AM' END as [TimeRange], SUM(Reading1), SUM(Reading2)
from #LocalTempTable
group by FORMAT(dateadd(hour,-7,[date]), 'yyyy-MM-dd') + case when DATEPART(hour,dateadd(hour,-7,[date])) between 0 and 11 then ' 7AM-7PM' ELSE ' 7PM-7AM' END
Assuming:
We need to SUM data for each day (not calculate total sum for all days)
We consider accuracy to minute, so 7AM = 420 minutes (from 0:00 AM) and 7PM = 1140 minutes
We split day to 2 group: group 1 > 7AM today and < 7PM today, group 2 >= 7PM today and <= 7AM tomorrow (E.G 20170228 will have 2 group:
20170228_1 and 20170228_2)
Then you could use this:
SELECT
CASE WHEN DATEPART(hh, date)*60 + DATEPART(mi, date) <= 420
THEN CONVERT(char(8), date - 1, 112) + '_2'
WHEN DATEPART(hh, date)*60 + DATEPART(mi, date) >= 1140
THEN CONVERT(char(8), date, 112) + '_2'
ELSE CONVERT(char(8), date, 112) + '_1'
END AS date_group,
SUM(reading1),
SUM(reading2)
FROM table_name
GROUP BY
CASE WHEN DATEPART(hh, date)*60 + DATEPART(mi, date) <= 420
THEN CONVERT(char(8), date - 1, 112) + '_2'
WHEN DATEPART(hh, date)*60 + DATEPART(mi, date) >= 1140
THEN CONVERT(char(8), date, 112) + '_2'
ELSE CONVERT(char(8), date, 112) + '_1'
END;
I have a log with fingerprint timestamps as follows:
Usr TimeStamp
-------------------------
1 2015-07-01 08:01:00
2 2015-07-01 08:05:00
3 2015-07-01 08:07:00
1 2015-07-01 10:05:00
3 2015-07-01 11:00:00
1 2015-07-01 12:01:00
2 2015-07-01 13:03:00
2 2015-07-01 14:02:00
1 2015-07-01 16:03:00
2 2015-07-01 18:04:00
And I wish an output of workers per hour (rounding to nearest hour)
The theoretical output should be:
7:00 0
8:00 3
9:00 3
10:00 2
11:00 1
12:00 2
13:00 1
14:00 2
15:00 2
16:00 1
17:00 1
18:00 0
19:00 0
Can anyone think on how to approach this as SQL or if no other way, through TSQL?
Edit: The timestamps are logins and logouts of the different users. So at 8am 3 users logged in and the same 3 are still working at 9am. One of them leaves at 10am. etc
To start with you can use datepart to get hours for the days as following and then use group by user
SELECT DATEPART(HOUR, GETDATE());
SQL Fiddle
SELECT Convert(varchar(5),DATEPART(HOUR, timestamp)) + ':00' as time,
count(usr) as users
from tbl
group by DATEPART(HOUR, timestamp)
You need a datetime hour table to do this.
Note : This is just a example of showing how the query should work for one day. Replace the CTE with datetime hour table. In datetime hour table every date should start with 07:00:00 hour and end with 19:00:00 hour
When you want to do this for more than one day then you may have to include the Cast(dt.date_time AS DATE) in select and group by to differentiate the hour belong to which day
WITH datetime_table
AS (SELECT '2015-07-01 07:00:00' AS date_time
UNION ALL
SELECT '2015-07-01 08:00:00'
UNION ALL
SELECT '2015-07-01 09:00:00'
UNION ALL
SELECT '2015-07-01 10:00:00'
UNION ALL
SELECT '2015-07-01 11:00:00'
UNION ALL
SELECT '2015-07-01 12:00:00'
UNION ALL
SELECT '2015-07-01 13:00:00'
UNION ALL
SELECT '2015-07-01 14:00:00'
UNION ALL
SELECT '2015-07-01 15:00:00'
UNION ALL
SELECT '2015-07-01 16:00:00'
UNION ALL
SELECT '2015-07-01 17:00:00'
UNION ALL
SELECT '2015-07-01 18:00:00'
UNION ALL
SELECT '2015-07-01 19:00:00')
SELECT Datepart(hour, dt.date_time),
Hour_count=Count(t.id)
FROM datetime_table dt
LEFT OUTER JOIN Yourtable t
ON Cast(t.dates AS DATE) = Cast(dt.date_time AS DATE)
AND Datepart(hour, t.dates) =
Datepart(hour, dt.date_time)
GROUP BY Datepart(hour, dt.date_time)
SQLFIDDLE DEMO
You just need to group by hours and date. Check this below query and hope this helps you:
Create table #t1
(
usr int,
timelog datetime
)
Insert into #t1 values(1, '2015-07-01 08:01:00')
Insert into #t1 values(2, '2015-07-01 08:05:00')
Insert into #t1 values(3, '2015-07-01 08:07:00')
Insert into #t1 values(1, '2015-07-01 10:05:00')
Insert into #t1 values(3, '2015-07-01 11:00:00')
Insert into #t1 values(1, '2015-07-01 12:01:00')
Insert into #t1 values(2, '2015-07-01 13:03:00')
Insert into #t1 values(2, '2015-07-01 14:02:00')
Insert into #t1 values(1, '2015-07-01 16:03:00')
Insert into #t1 values(2, '2015-07-01 18:04:00')
Select cast(timelog as varchar(11)) as LogDate, Datepart(hour, timelog) as LogTime, count(usr) as UserCount from #t1
Group by Datepart(hour, timelog), cast(timelog as varchar(11))
The harder part is creating the zeros where data is missing. The usual approach is to generate a list of all possible "slots" and then do an outer join to the actual data. I'm assuming that you only want to run this for a single day at a time.
My approach, which is just an example, works because it does a cross join of two tables with 6 and 4 rows respectively and 6 times 4 is 24.
select f1.d * 6 + f0.d, coalesce(data.cnt, 0)
from
(
select 0 as d union all select 1 union all select 2 union all
select 3 union all select 4 union all select 5
) as f0,
(
select 0 as d union all select 1 union all
select 2 union all select 3
) as f1
left outer join
(
select
cast(datepart(hh, TimeStamp) as varchar(2)) + ':00' as hr,
count(*) as cnt
from LOG
group by datepart(hh, TimeStamp)
) as data
on data.hr = f1.d * 6 + f0.d
First you need to round up time to the closest hour
DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(MI, 30, TimeStamp)), 0)
As you see first we add 30 minutes to the original time (DATEADD(MI, 30, TimeStamp))
This approach will round up 08:04 to 08:00 or 07:58 to 8:00 too.
As I assume some workers can start working little bid early
SELECT DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(MI, 30, TimeStamp)), 0) As FingertipTime
FROM Fingertips
You can create a Computed column if you use rounded timestamp often
ALTER TABLE Fingertips ADD RoundedTimeStamp AS (DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(MI, 30, TimeStamp)), 0));
For comparing timestamps with constants of work hours you can find different methods. I will use a variable of type TABLE where i generate work hours for current day
Then using LEFT JOIN and GROUP BY we get quantity of timestamps
DECLARE #WorkHours TABLE(WorkHour DATETIME)
INSERT INTO #WorkHours (WorkHour) VALUES
('2015-07-01 07:00'),
('2015-07-01 08:00'),
('2015-07-01 09:00'),
('2015-07-01 10:00'),
('2015-07-01 11:00'),
('2015-07-01 12:00'),
('2015-07-01 13:00'),
('2015-07-01 14:00'),
('2015-07-01 15:00'),
('2015-07-01 16:00'),
('2015-07-01 17:00'),
('2015-07-01 18:00'),
('2015-07-01 19:00')
SELECT wh.Workhour
, COUNT(ft.TimeStamp) As Quantity
FROM #WorkHours wh
LEFT JOIN Fingertips ft ON ft.RoundedTimeStamp = wh.WorkHour
GROUP BY wh.WorkHour
Check this SQL Fiddle
Many separate parts that have to be glued together to get this done.
First rounding, this is easily done with obtaining the hour part of the date + 30 minutes. Then determine start and end records. If there are no fields to indicate this and assuming the first occurrence of a day is the login or start, you can use row_number and use the odd numbers as start records.
Then start and end have to be coupled, in sql server 2012 and higher this can be easily done with the lead function
To get the missing hours a sequence has to be created with all the hours. Several options for this (good link here), but I like the approach of using row_number on a table that is sure to contain enough rows (with a proper column for order by), such as sys.all_objects used in the link. That way hours 7 to 19 could be created as: select top 13 ROW_NUMBER() over (order by object_id) + 6 [Hour] from sys.all_objects
If there's only one date to check on, the query can simple left join on the hour of the timestamp fingerprints. If there are more dates, a second sequence could be created cross applied to the times to get all dates. Assuming the one date, final code would be:
declare #t table(Usr int, [timestamp] datetime)
insert #t values
(1 , '2015-07-01 08:01:00'),
(2 , '2015-07-01 08:05:00'),
(3 , '2015-07-01 08:07:00'),
(1 , '2015-07-01 10:05:00'),
(3 , '2015-07-01 11:00:00'),
(1 , '2015-07-01 12:01:00'),
(2 , '2015-07-01 13:03:00'),
(2 , '2015-07-01 14:02:00'),
(1 , '2015-07-01 16:03:00'),
(2 , '2015-07-01 18:04:00'),
(2 , '2015-07-01 18:04:00')
;with usrHours as
(
select Usr, datepart(hour, DATEADD(minute,30, times.timestamp)) [Hour] --convert all times to the rounded hour (rounding by adding 30 minutes)
, ROW_NUMBER() over (partition by usr order by [timestamp] ) rnr
from #t times --#t should be your logging table
), startend as --get next (end) hour by using lead
(
select Usr, [hour] StartHour , LEAD([Hour]) over (partition by usr order by rnr) NextHour ,rnr
from usrHours
),hours as --sequence of hours 7 to 19
(
select top 13 ROW_NUMBER() over (order by object_id) + 6 [Hour] from sys.all_objects
)
select cast([Hour] as varchar) + ':00' [Hour], COUNT(startend.usr) Users
from hours --sequence is leading
left join startend on hours.Hour between startend.StartHour and startend.NextHour
and rnr % 2 = 1 --every odd row number is a start time
group by Hours.hour
Here is my final working code:
create table tsts(id int, dates datetime)
insert tsts values
(1 , '2015-07-01 08:01:00'),
(2 , '2015-07-01 08:05:00'),
(3 , '2015-07-01 08:07:00'),
(1 , '2015-07-01 10:05:00'),
(3 , '2015-07-01 11:00:00'),
(1 , '2015-07-01 12:01:00'),
(2 , '2015-07-01 13:03:00'),
(2 , '2015-07-01 14:02:00'),
(1 , '2015-07-01 16:03:00'),
(2 , '2015-07-01 18:04:00')
select horas.hora, isnull(sum(math) over(order by horas.hora rows unbounded preceding),0) as Employees from
(
select 0 as hora union all
select 1 as hora union all
select 2 as hora union all
select 3 as hora union all
select 4 as hora union all
select 5 as hora union all
select 6 as hora union all
select 7 as hora union all
select 8 as hora union all
select 9 as hora union all
select 10 as hora union all
select 11 as hora union all
select 12 as hora union all
select 13 as hora union all
select 14 as hora union all
select 15 as hora union all
select 16 as hora union all
select 17 as hora union all
select 18 as hora union all
select 19 as hora union all
select 20 as hora union all
select 21 as hora union all
select 22 as hora union all
select 23
) as horas
left outer join
(
select hora, sum(math) as math from
(
select id, hora, iif(rowid%2 = 1,1,-1) math from
(
select row_number() over (partition by id order by id, dates) as rowid, id, datepart(hh,dateadd(mi, 30, dates)) as hora from tsts
) as Q1
) as Q2
group by hora
) as Q3
on horas.hora = Q3.hora
SQL Fiddle