Splitting up events that occur over the day boundary - sql

I have a table of events with a start time and an end time, with some events that have a start time before midnight and an end time after midnight. I'd like to produce output that splits up these events at the midnight barrier so they can be counted toward their respective date.
| EVENT_ID | START_TIME | END_TIME |
|----------|-------------------------|-------------------------|
| 1001 | 2021-02-21 14:00:00.000 | 2021-02-21 18:00:00.000 |
| 1002 | 2021-02-21 17:00:00.000 | 2021-02-22 03:00:00.000 |
| 1003 | 2021-02-21 18:00:00.000 | 2021-02-21 22:00:00.000 |
| 1004 | 2021-02-21 22:00:00.000 | 2021-02-22 07:00:00.000 |
The above table could be produced by the query:
SELECT EVENT_ID,
START_TIME,
END_TIME
FROM EVENTS
WHERE START_TIME BETWEEN '2021-02-21 00:00:00.000' AND '2021-02-21 23:59:59.999'
;
My desired output will split up the events that span multiple days at midnight:
| EVENT_ID | START_TIME | END_TIME |
|----------|-------------------------|-------------------------|
| 1001 | 2021-02-21 14:00:00.000 | 2021-02-21 18:00:00.000 |
| 1002 | 2021-02-21 17:00:00.000 | 2021-02-21 23:59:59.999 |
| 1002 | 2021-02-22 00:00:00.000 | 2021-02-22 03:00:00.000 |
| 1003 | 2021-02-21 18:00:00.000 | 2021-02-21 22:00:00.000 |
| 1004 | 2021-02-21 22:00:00.000 | 2021-02-21 23:59:59.999 |
| 1004 | 2021-02-22 00:00:00.000 | 2021-02-22 07:00:00.000 |
Any help would be greatly appreciated. Ideally I'd like to produce this without functions or the creation of new tables.
Note that I'm using SQL Server 2016

Using table of numbers
with t0(n) as (
select n
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
) t(n)
),nmbs as(
select row_number() over(order by t1.n) - 1 n
from t0 t1 cross join t0 t2 cross join t0 t3
)
select event_id,
case when n = 0
then start_time
else dateadd(day, n, convert(date, start_time))
end start_time,
case when datediff(day, start_time, end_time) = n
then end_time
else dateadd(second, -1, dateadd(day, n + 1, convert(datetime, convert(date, start_time))))
end as end_time
from Events
cross apply (
select top (datediff(day, start_time, end_time) + 1) n
from nmbs) ns

You can use a recursive CTE for this:
with cte as (
select event_id, start_time,
(case when datediff(day, start_time, end_time) = 0 then end_time
else dateadd(day, 1, convert(date, start_time))
end) as end_time,
end_time as real_end_time
from t
union all
select event_id, end_time,
(case when dateadd(day, 1, convert(date, end_time)) > real_end_time
then real_end_time
else dateadd(day, 1, convert(date, end_time))
end),
real_end_time
from cte
where end_time < real_end_time
)
select *
from cte;
Here is a db<>fiddle.

The following method solves for the case of midnight between START_TIME and END_TIME. The "desired output" above indicates only a single midnight occurs between START_TIME and END_TIME.
IF OBJECT_ID('tempdb..#t') IS NOT NULL DROP TABLE #t
CREATE TABLE #t ( Event_ID INT, START_TIME DATETIME2, END_TIME DATETIME2)
INSERT INTO #t (Event_ID, START_TIME, END_TIME)
VALUES
( 1001, '2021-02-21 14:00:00.000', '2021-02-21 18:00:00.000' )
, ( 1002, '2021-02-21 17:00:00.000', '2021-02-22 03:00:00.000' )
, ( 1003, '2021-02-21 18:00:00.000', '2021-02-21 22:00:00.000' )
, ( 1004, '2021-02-21 22:00:00.000', '2021-02-22 07:00:00.000' )
-- get original data plus midnight after START_TIME
IF OBJECT_ID('tempdb..#stage') IS NOT NULL DROP TABLE #stage
SELECT *
, CONVERT(DATETIME2, CONVERT(DATE, DATEADD(DAY, 1, t.START_TIME))) d
INTO #stage
FROM #t t
-- get all rows
SELECT Event_ID, START_TIME
, CASE WHEN d > END_TIME THEN END_TIME ELSE d END END_TIME
FROM #stage
UNION ALL
-- get rows where midnight occurs between START_TIME and END_TIME
SELECT Event_ID
, CASE WHEN d > END_TIME THEN START_TIME ELSE d END START_TIME
, END_TIME
FROM #stage
WHERE d < END_TIME
ORDER BY Event_ID

Related

Time until midnight between two date and time columns

I need help with my query to calculate the time until midnight between two date and time columns
break down by day
This is the main table:
ID
Start_Time
End_time
DateDiff
32221
01-01-2022 13:10:00
01-03-2022 13:10:00
2880
My query:
SELECT
start_time.ID,
start_time.Date_Time AS Start_time,
end_time.Date_Time AS End_time,
DATEDIFF(minute, start_time.Date_Time, end_time.Date_Time) AS DateDiff
FROM
Main
what I need is similar to this:
ID
Date_start
End_time
DateDiff
32221
01-01-2022 13:10:00
01-01-2022 23:59:59
654
32221
01-02-2022 00:00:00
01-02-2022 23:59:59
1440
32221
01-03-2022 00:00:00
01-03-2022 13:10:00
781
how i can do that?
You can loop through the times, always adding the time untill midnight, untill your 'start_time + 1 day' is bigger than your end_time.
The below code can be run directly in SQL (mind the date notation, my SQL is in united states notation, so if yours is in Europe this will give you back results for 3 months instead of 3 days);
DECLARE #start_time datetime2 = '01/01/2022 13:00:00';
DECLARE #end_time datetime2 = '03/01/2022 14:00:00';
DECLARE #daily_end_time datetime2=NULL;
DECLARE #Table Table (start_time datetime2, end_time datetime2, diff nvarchar(8));
DECLARE #diff_minutes_start int = DATEDIFF(MINUTE,#start_time,DateDiff(day,0,dateadd(day,1,#start_time)));
DECLARE #diff_minutes_end int = DATEDIFF(minute,#end_time,DateDiff(day,0,dateadd(day,1,#end_time)))
SET #daily_end_time = DATEADD(mi,#diff_minutes_start,#start_time)
WHILE #daily_end_time < #end_time
BEGIN
INSERT INTO #Table (start_time,end_time,diff)
VALUES (
#start_time,
CASE WHEN DATEADD(day,1,#daily_end_time) > #end_time THEN #end_time ELSE
#daily_end_time END,
CASE WHEN DATEADD(day,1,#daily_end_time) > #end_time THEN #diff_minutes_end ELSE
#diff_minutes_start END )
SET #daily_end_time = DATEADD(mi,#diff_minutes_start,#start_time)
SET #start_time = DATEADD(mi,1,#daily_end_time);
select #diff_minutes_start =
DATEDIFF(MINUTE,#start_time,DateDiff(day,0,dateadd(day,1,#start_time)));
select #diff_minutes_end = DATEDIFF(minute,#end_time,DateDiff(day,0,dateadd(day,1,#end_time)))
END
SELECT * FROM #Table
And the results:
You may use a recursive CTE as the following:
With CTE As
(
Select ID, Start_Time, End_time, DATEADD(Second, -1, DATEADD(Day, DATEDIFF(Day,0, Start_Time), 1)) et
From main
Union All
Select C.ID, DATEADD(Second, 1, C.et), C.End_time, DATEADD(Day, 1, C.et)
From CTE C Join main T
On C.ID = T.ID
Where DATEADD(Second, 1, C.et) <= C.End_time
)
Select ID, Start_Time,
Case When End_Time <= et Then End_Time Else et End As End_Time,
DATEDIFF(Minute, Start_Time, DATEADD(Second, 1, Case When End_Time <= et Then End_Time Else et End)) As [DateDiff]
From CTE
Order By ID, Start_Time
See a demo with extended data sample from db<>fiddle.
You can also solve this with a tally table, using the expanded (to show different cases) sample data
ID
StartTime
EndTime
32221
2022-01-01 13:10:00
2022-01-03 13:10:00
32222
2022-02-02 10:10:00
2022-02-02 17:10:00
32223
2022-03-03 19:10:00
2022-03-04 08:10:00
32224
2022-04-04 19:10:00
2022-04-08 08:10:00
and the code
with cteSampleData as ( --Enter some sample data, include spans of 0, 1, and >1 days
SELECT * --Note that we need CONVERT to make sure the dates are treated as datetime not string!
FROM (VALUES(32221, CONVERT(datetime2(0), '01-01-2022 13:10:00'), CONVERT(datetime2(0), '01-03-2022 13:10:00') )
, (32222, '02-02-2022 10:10:00', '02-02-2022 17:10:00')
, (32223, '03-03-2022 19:10:00', '03-04-2022 08:10:00')
, (32224, '04-04-2022 19:10:00', '04-08-2022 08:10:00')
) as Samp(ID, StartTime, EndTime)
), cteWithControl as ( --Add some fields to make testing cledarer - you could do this as part of a subsequent step instead
SELECT *
, CONVERT(date, StartTime) as StartDate , CONVERT(date, EndTime) as EndDate
, DATEDIFF(day, StartTime , EndTime) as DiffDays
--, DATEDIFF(day, CONVERT(date, StartTime) , CONVERT(date, EndTime)) as DiffDays
FROM cteSampleData
), cteTally as ( --Get a list of integers to represent days, assume nothing lasts longer than a year
SELECT top 365 ROW_NUMBER() over (ORDER BY name) as Tally
FROM sys.objects --just a table we know has over 300 rows, look up tally tables for other generation methods
)--The real work begins below, partition the data into "same day" and "multi-day" spans
, cteSet as (
SELECT ID, StartTime, EndTime, DiffDays, 1 as DayNumber
FROM cteWithControl WHERE DiffDays = 0
UNION ALL
SELECT ID --For multi-day, cross with the tally table and treat first and last days special
, CASE WHEN T.Tally = 1 THEN StartTime --For the first day the start time is the real time
ELSE DATEADD (day, T.Tally - 1, startdate) END as StartTime --Otherwise it's the start of the day
, CASE WHEN T.Tally = DiffDays + 1 THEN EndTime --For the last day the end is the real end
ELSE DATEADD (second, -1, CONVERT(DATETIME2(0), DATEADD (day, T.Tally, startdate)))
END as EndTime --otherwise 1 second less than the next day
, DiffDays, Tally as DayNumber
FROM cteWithControl as D CROSS JOIN cteTally as T
WHERE DiffDays > 0 AND T.Tally <= D.DiffDays + 1
)--Now we display the results and calculate the length (in minutes) of each span
SELECT *
, DATEDIFF(MINUTE, StartTime, EndTime) as DateDiff
FROM cteSet
ORDER BY ID, DayNumber
we get the output
ID
StartTime
EndTime
DiffDays
DayNumber
DateDiff
32221
2022-01-01 13:10:00
2022-01-01 23:59:59
2
1
649
32221
2022-01-02 00:00:00
2022-01-02 23:59:59
2
2
1439
32221
2022-01-03 00:00:00
2022-01-03 13:10:00
2
3
790
32222
2022-02-02 10:10:00
2022-02-02 17:10:00
0
1
420
32223
2022-03-03 19:10:00
2022-03-03 23:59:59
1
1
289
32223
2022-03-04 00:00:00
2022-03-04 08:10:00
1
2
490
32224
2022-04-04 19:10:00
2022-04-04 23:59:59
4
1
289
32224
2022-04-05 00:00:00
2022-04-05 23:59:59
4
2
1439
32224
2022-04-06 00:00:00
2022-04-06 23:59:59
4
3
1439
32224
2022-04-07 00:00:00
2022-04-07 23:59:59
4
4
1439
32224
2022-04-08 00:00:00
2022-04-08 08:10:00
4
5
490

List all date and time between two dates in SQL Server

I need help with my query to list all dates and time between two date and time columns
This is the main table:
ID
Date_Time
Action_c
Action_type
32221
01-01-2022 13:10:00
1
Start
32221
01-03-2022 13:10:00
2
End
I used CTEs to get the start time and end time in one row
WITH start_time AS
(
SELECT *
FROM main
WHERE action_c = 1
),
end_time AS
(
SELECT *
FROM main
WHERE action_c = 2
)
SELECT
start_time.ID,
start_time.Date_Time AS Start_time,
end_time.Date_Time AS End_time,
DATEDIFF(minute, start_time.Date_Time, end_time.Date_Time) AS DateDiff
FROM
start_time
INNER JOIN
end_time ON start_time.ID = end_time.ID
Results:
ID
Start_Time
End_time
DateDiff
32221
01-01-2022 13:10:00
01-03-2022 13:10:00
2880
But what I actually need is :
ID
Start_Time
End_time
DateDiff
32221
01-01-2022 13:10:00
01-02-2022 13:10:00
1440
32221
01-02-2022 13:10:00
01-03-2022 13:10:00
1440
Not sure how I can do that without creating any function.
Here is an example where I add an ID and the EndTime <> StartTime
Example or dbFiddle
Declare #YourTable Table ([ID] int,[Date_Time] datetime,[Action_c] int,[Action_type] varchar(50))
Insert Into #YourTable Values
(32221,'01-01-2022 13:10:00',1,'Start')
,(32221,'01-03-2022 13:10:00',2,'End')
,(99999,'01-01-2022 13:10:00',1,'Start') -- Added
,(99999,'01-04-2022 10:00:00',2,'End') -- Added End Time<>Start Time
;with cte1 as (
Select ID
,DR1 = min(case when Action_C=1 then Date_Time end)
,DR2 = max(case when Action_C=2 then Date_Time end)
,nDays= datediff(day, min(case when Action_C=1 then Date_Time end),max(case when Action_C=2 then Date_Time end) )
From #YourTable
Group By ID
), cte2 as (
Select Top 1000 N=-1+Row_Number() Over (Order By (Select NULL)) From master..spt_values n1, master..spt_values n2
)
Select ID
,c.Start_Time
,c.End_Time
,DateDiff = datediff(minute, c.Start_Time,c.End_Time )
From cte1
Join cte2 on N< nDays
Cross Apply ( values ( dateadd(DAY,N,DR1),case when N=nDays-1 then DR2 else dateadd(DAY,N+1,DR1) end ) )C(Start_Time,End_Time)
Order by ID,Start_Time
Results
ID Start_Time End_Time DateDiff
32221 2022-01-01 13:10:00.000 2022-01-02 13:10:00.000 1440
32221 2022-01-02 13:10:00.000 2022-01-03 13:10:00.000 1440
99999 2022-01-01 13:10:00.000 2022-01-02 13:10:00.000 1440
99999 2022-01-02 13:10:00.000 2022-01-03 13:10:00.000 1440
99999 2022-01-03 13:10:00.000 2022-01-04 10:00:00.000 1250 -- Note

Time difference between two times in Hours in SQL Server

I need the time difference between two times in Hours. I have the start time and end time as shown below:
Start time | End Time
-----------+----------
23:00:00 | 19:00:00
23:00:00 | 07:00:00
I need the output for first row as 20, for second row 8.
Try this:
Schema:
create table a(Starttime time,Endtime time)
INSERT INTO a VALUES ('23:00:00','19:00:00')
INSERT INTO a VALUES ('09:00:00','19:00:00')
INSERT INTO a VALUES ('23:00:00','07:00:00')
Query:
select Starttime,Endtime,
CASE WHEN datediff(HOUR,Starttime,Endtime)<0 THEN 24+datediff(HOUR,Starttime,Endtime)
ELSE datediff(HOUR,Starttime,Endtime) END Diff
FROM A
Output:
| Starttime | Endtime | Diff |
|------------------|------------------|------|
| 23:00:00.0000000 | 19:00:00.0000000 | 20 |
| 09:00:00.0000000 | 19:00:00.0000000 | 10 |
| 23:00:00.0000000 | 07:00:00.0000000 | 8 |
Use DATEDIFF:
SELECT
start_time,
end_time,
24 + DATEDIFF(HOUR, start_time, end_time) AS diff_in_hours
FROM yourTable;
Demo
Query as per your requirement, just put your table name at the place of "YourTable"
SELECT Starttime
,Endtime
,CASE
WHEN DATEDIFF(HOUR, Starttime, Endtime) < 0
THEN 24 + DATEDIFF(HOUR, Starttime, Endtime)
ELSE DATEDIFF(HOUR, Starttime, Endtime)
END Time_Difference
FROM YourTable
Use select case
select case when start_time > end_time
then datediff(hour, start_time , dateadd(hh, 24, end_Time))
else datediff(hh, start_time , end_Time) end

Group by data to get count between the datetime range

Let's say I've a table like below
start_time end_time user_name
2019-01-01 00:00:05 2019-01-01 00:05:05 user1
2019-01-01 00:01:35 2019-01-01 00:06:05 user2
2019-01-01 00:02:05 2019-01-01 00:07:05 user3
2019-01-01 00:03:05 2019-01-01 00:08:05 user1
2019-01-01 00:04:05 2019-01-01 00:09:05 user2
My objective is find out how many users were logged in for a MINUTE. Say like below
time active no of users
2019-01-01 00:00:00 1
2019-01-01 00:01:00 2
2019-01-01 00:02:00 3
2019-01-01 00:03:00 3
2019-01-01 00:04:00 3
Now I first tried to round of time for a new column dateadd(mi, datediff(mi, 0, dateadd(s, 30, start_time)), 0). So, I will receive like above table time column
Next I tried to find the count for rounded datetime like below
SELECT
dateadd(mi, datediff(mi, 0, dateadd(s, 30, start_time)), 0) as RoundedDateTime,
(
SELECT count(distinct(user_name))
FROM entrytable sh
WHERE (sh.end_time > dateadd(mi, datediff(mi, 0, dateadd(s, 30, t.start_time)), 0)
and sh.start_time <= dateadd(mi, datediff(mi, 0, dateadd(s, 30, t.start_time)), 0))
) as usercounter
FROM entrytable t
But, above SQL query is running for longer time and goes to not responding mode.
I could not fix the issue. Can someone help?
Thanks in advance!
The most trivial solution is this:
DECLARE #t TABLE (start_time datetime, end_time datetime, user_name varchar(10));
INSERT INTO #t VALUES
('2019-01-01 00:00:05', '2019-01-01 00:05:05', 'user1'),
('2019-01-01 00:01:35', '2019-01-01 00:06:05', 'user2'),
('2019-01-01 00:02:05', '2019-01-01 00:07:05', 'user3'),
('2019-01-01 00:03:05', '2019-01-01 00:08:05', 'user1'),
('2019-01-01 00:04:05', '2019-01-01 00:09:05', 'user2');
SELECT dt AS date_time, SUM(SUM(val)) OVER (ORDER BY dt) AS active_count
FROM (
SELECT start_time, +1 FROM #t UNION ALL
SELECT end_time, -1 FROM #t
) cte1(dt, val)
GROUP BY dt
This will give you the number of active users whenever there was a change (someone logged in or logged out). Result:
| date_time | active_count |
|-------------------------|--------------|
| 2019-01-01 00:00:05.000 | 1 |
| 2019-01-01 00:01:35.000 | 2 |
| 2019-01-01 00:02:05.000 | 3 |
| 2019-01-01 00:03:05.000 | 4 |
| 2019-01-01 00:04:05.000 | 5 |
| 2019-01-01 00:05:05.000 | 4 |
| 2019-01-01 00:06:05.000 | 3 |
| 2019-01-01 00:07:05.000 | 2 |
| 2019-01-01 00:08:05.000 | 1 |
| 2019-01-01 00:09:05.000 | 0 |
Be advised that the result does not contain the "in-between" dates.
This question was originally tagged for SQL Server 2012, so this answer is for SQL Server.
One method is to generate a list of minutes and then:
with minutes as (
select cast('2019-01-01 00:00:00' as datetime) as mm
union all
select dateadd(minute, 1, minute)
from cte
where mm < '2019-01-01 00:00:05'
)
select m.*,
(select count(*)
from entrytable et
where et.start_time <= m.mm and
et.end_time > m.mm
) as num_actives
from minutes m;

Sum of data between 7AM to 7PM and 7PM to 7AM (next day)

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;