Find Overlap In Time Records - sql

I have a table in a SQL Server 2012 database that logs events, with columns StartDate and EndDate. I need to aggregate all of the records within a certain time period, and determine the duration of time where any events were active, not counting overlapping durations. For example, if my table looks like this:
id StartDate EndDate
=======================================================
1 2017-08-28 12:00:00 PM 2017-08-28 12:01:00 PM
2 2017-08-28 1:15:00 PM 2017-08-28 1:17:00 PM
3 2017-08-28 1:16:00 PM 2017-08-28 1:20:00 PM
4 2017-08-28 1:30:00 PM 2017-08-28 1:35:00 PM
And my time period to search was from 2017-08-28 12:00:00 PM to 2017-08-28 2:00:00 PM, then my desired output should be:
Duration of Events Active = 00:11:00
I have been able to aggregate the records and get a total duration (basically just EndDate - StartDate), but I cannot figure out how to exclude overlapping time. Any help would be appreciated. Thanks!

How about a CTE and aggregation? This can be done with a sub-query too.
declare #table table (id int, StartDate datetime, EndDate datetime)
insert into #table
values
( 1,'2017-08-28 12:00:00 PM','2017-08-28 12:01:00 PM'),
(2,'2017-08-28 1:15:00 PM','2017-08-28 1:17:00 PM'),
(3,'2017-08-28 1:16:00 PM','2017-08-28 1:20:00 PM'),
(4,'2017-08-28 1:30:00 PM','2017-08-28 1:35:00 PM')
declare #StartDate datetime = '2017-08-28 12:00:00'
declare #EndDate datetime = '2017-08-28 14:00:00'
;with cte as(
select
id
,StartDate = case when StartDate < lag(EndDate) over (order by id) then lag(EndDate) over (order by id) else StartDate end
,EndDate
from
#table
where
StartDate >= #StartDate
and EndDate <= #EndDate),
cte2 as(
select
Dur = datediff(second, StartDate, EndDate)
from cte)
select
Dur = convert(varchar, dateadd(ms, sum(Dur) * 1000, 0), 114)
from
cte2

Related

How to split the time range into multiple rows

I want to split the date/time ranges into multiple rows by hour in SQL Server but have some issues. My current dataset looks like this:
EmployeeCode StartDateTime EndDateTime
843578 2017-05-14 8:30 AM 2017-05-14 11:36 PM
587123 2017-05-14 22:00 PM 2017-05-15 01:28 AM
And I want something like this as my result table. Note that I want to treat a block less than an hour as one independent row as well. (For example 8:30AM - 9:00AM as one row.)
EmployeeCode StartDateTime EndDateTime
843578 2017-05-14 8:30 AM 2017-05-14 9:00 PM
843578 2017-05-14 9:00 AM 2017-05-14 10:00 AM
843578 2017-05-14 10:00 AM 2017-05-14 11:00 AM
843578 2017-05-14 11:00 AM 2017-05-14 11:36 AM
587123 2017-05-14 22:00 PM 2017-05-14 23:00 PM
587123 2017-05-14 23:00 PM 2017-05-15 00:00 AM
587123 2017-05-15 00:00 AM 2017-05-15 01:00 AM
587123 2017-05-15 01:00 AM 2017-05-15 01:28 AM
My current code only splits the date/time range that is within the same day. For example, the time range for Employee 587123 stops the spliting at 22:00 - 23:00 and doesn't work for the time range in next day.
How do I update my code to capture data after midnight? (The last three rows in the sample result table.)
Here's my current code
SELECT YT.EmployeeCode,
CASE WHEN YT.StartDateTime > DT.StartDateTime THEN YT.StartDateTime ELSE DT.StartDateTime END AS StartDateTime,
CASE WHEN YT.EndDateTime < DT.EndDateTime THEN YT.EndDateTime ELSE DT.EndDateTime END AS StartDateTime
FROM (VALUES(843578,CONVERT(datetime2(0),'2017-05-14T08:30:00'),CONVERT(datetime2(0),'2017-05-14T15:36:00')),
(587123,CONVERT(datetime2(0),'2017-05-14T09:00:00'),CONVERT(datetime2(0),'2017-05-14T18:28:00')))YT(EmployeeCode,StartDateTime,EndDateTime)
CROSS APPLY (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23))T(I)
CROSS APPLY (VALUES(DATEADD(HOUR,T.I,CONVERT(time(0),'00:00:00')),DATEADD(HOUR,T.I+1,CONVERT(time(0),'00:00:00'))))V(StartTime,EndTime)
CROSS APPLY (VALUES(DATETIMEFROMPARTS(YEAR(YT.StartDateTime),MONTH(YT.StartDateTime),DAY(YT.StartDateTime),DATEPART(HOUR,V.StartTime),DATEPART(MINUTE,V.StartTime),0,0),
DATETIMEFROMPARTS(YEAR(YT.StartDateTime),MONTH(YT.StartDateTime),DAY(YT.StartDateTime),DATEPART(HOUR,V.EndTime),DATEPART(MINUTE,V.EndTime),0,0)))DT(StartDateTime,EndDateTime)
WHERE YT.StartDateTime <= DT.EndDateTime
AND YT.EndDateTime >= DT.StartDateTime;
The current code looks too complicated so if you know better way to do this, please let me know.
I'd appreciate any help on this.
Here is a recursive CTE solution:
with cte as (
select
employeecode,
startdatetime,
dateadd(hour, 1, datetimefromparts(year(startdatetime), month(startdatetime), day(startdatetime), datepart(hour, startdatetime), 0, 0, 0)) enddatetime
enddatetime maxdatetime
from mytable
union all
select employeecode, enddatetime, dateadd(hour, 1, enddatetime), maxdatetime
from cte
where enddatetime < maxdatetime
)
select employeecode, startdatetime,
case when enddatetime < maxdatetime then enddatetime else maxdatetime end as enddatetime
from cte
Basically, the anchor of the CTE performs computes the end of the first range, using datetimefrompart(). Then we iteratively generate the following ranges, until the maximum date time is reached. We can then display the results with the outer query, while adjusting the end date of the last range.
I would approach this using a recursive CTE:
with cte as (
select t.EmployeeCode, t.StartDateTime as startdt,
dateadd(hour, datepart(hour, t.startdatetime) + 1, convert(datetime, convert(date, t.StartDateTime))) as enddt,
t.endDateTime, 1 as lev
from t
union all
select cte.employeecode, enddt,
(case when dateadd(hour, 1, enddt) < enddatetime then dateadd(hour, 1, enddt) else enddatetime end),
enddatetime, lev + 1
from cte
where enddt < enddatetime
)
select *
from cte
order by employeecode, startdt;
Here is a db<>fiddle.
If you might have spans of more than 100 hours, then you need option (maxrecursion 0) for the query.

Split date/time ranges into multiple rows

I'm trying to split the date/time ranges in my dataset into multiple rows by hour in SQL Server. My current dataset looks like:
EmployeeCode StartDateTime EndDateTime
843578 2017-05-14 8:30 AM 2017-05-14 3:36 PM
587123 2017-05-14 9:00 AM 2017-05-14 6:28 PM
.....
And my current code seems like it's not catching both Date and Time element in the dataset. I want something like this as my final result.
EmployeeCode StartDateTime EndDateTime
843578 2017-05-14 8:30 AM 2017-05-14 9:00 PM
843578 2017-05-14 9:00 AM 2017-05-14 10:00 AM
843578 2017-05-14 10:00 AM 2017-05-14 11:00 AM
843578 2017-05-14 11:00 AM 2017-05-14 12:00 PM
843578 2017-05-14 12:00 PM 2017-05-14 01:00 PM
843578 2017-05-14 01:00 PM 2017-05-14 02:00 PM
843578 2017-05-14 02:00 PM 2017-05-14 03:00 PM
843578 2017-05-14 03:00 PM 2017-05-14 03:36 PM
587123 2017-05-14 9:00 AM 2017-05-14 10:00 AM
...............
I want to capture both date and time information and expand it to the rows by hour. Also, if an entry starts at 8:30 I want to split it as a separate unit such as 8:30 to 9:00 and the same logic is applied to the end time. (e.g. 3:00 - 3:36) My current code is not complete and I'd appreciate how I should rewrite the code to achieve what I mentioned above.
Select EmployeeCode, StartDateTime, EndDateTime
,StartTime = case when N=datepart(hour,StartDateTime) then StartTime else TimeFromParts(N,0,0,0,0)
end
,EndTime = case when N=datepart(hour,EndDateTime) then EndTime else
TimeFromParts(N+1,0,0,0,0) end
From mytable T
Join ( values (0),(1),(2),(3),(4),(5),(6)
,(7),(8),(9),(10),(11),(12),(13)
,(14),(15),(16),(17),(18),(19),(20)
,(21),(22),(23)
) B(N)
on N between datepart(hour,StartDateTime) and datepart(hour,EndDateTime)
This is a bit messy, but using a few table constructors, you can create a tally and time ranges. Then you use a CASE expression to return the relevant values:
SELECT YT.EmployeeCode,
CASE WHEN YT.StartDateTime > DT.StartDateTime THEN YT.StartDateTime ELSE DT.StartDateTime END AS StartDateTime,
CASE WHEN YT.EndDateTime < DT.EndDateTime THEN YT.EndDateTime ELSE DT.EndDateTime END AS StartDateTime
FROM (VALUES(843578,CONVERT(datetime2(0),'2017-05-14T08:30:00'),CONVERT(datetime2(0),'2017-05-14T15:36:00')),
(587123,CONVERT(datetime2(0),'2017-05-14T09:00:00'),CONVERT(datetime2(0),'2017-05-14T18:28:00')))YT(EmployeeCode,StartDateTime,EndDateTime)
CROSS APPLY (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23))T(I)
CROSS APPLY (VALUES(DATEADD(HOUR,T.I,CONVERT(time(0),'00:00:00')),DATEADD(HOUR,T.I+1,CONVERT(time(0),'00:00:00'))))V(StartTime,EndTime)
CROSS APPLY (VALUES(DATETIMEFROMPARTS(YEAR(YT.StartDateTime),MONTH(YT.StartDateTime),DAY(YT.StartDateTime),DATEPART(HOUR,V.StartTime),DATEPART(MINUTE,V.StartTime),0,0),
DATETIMEFROMPARTS(YEAR(YT.StartDateTime),MONTH(YT.StartDateTime),DAY(YT.StartDateTime),DATEPART(HOUR,V.EndTime),DATEPART(MINUTE,V.EndTime),0,0)))DT(StartDateTime,EndDateTime)
WHERE YT.StartDateTime <= DT.EndDateTime
AND YT.EndDateTime >= DT.StartDateTime;
From the comments, it looks(?) like the OP is using a Procedure to get the data, not a table as alluded to in the question, as they say that they can't use a FROM. You'll need to create a table, and then INSERT that data into it and reference it in the FROM:
CREATE TABLE #t (EmployeeCode int,
StartDatetime datetime2(0),
EndDateTime datetime2(0));
INSERT INTO #t
EXEC dbo.YourProc {Parameters?};
--Then prior solution:
SELECT YT.EmployeeCode,
...
FROM #T YT
CROSS APPLY (VALUES(0),(1)....
Does this do what you need?
Select
EmployeeCode
, final.startDateTime
, final.endDateTime
from
myTable as employee
cross apply ( Select
startDate = convert(date, startDateTime)
, startHour = datepart(hour, startDateTime)
, endDate = convert(date, endDateTime)
, endHour = datepart(hour, endDateTime)
) as times
cross apply (Select
roundedStart = dateadd(hour, startHour, convert(datetime, startDate))
, roundedEnd = dateAdd(hour, endHour + 1, convert(datetime, endDate))
) as rounding
cross apply (Select
[hours] = datediff(hour,roundedStart, roundedEnd)
) as diff
cross apply (Select
increment = ROW_NUMBER() over (order by (select NULL))
from string_split(replicate(' ',diff.[hours] - 1),' ')
) as repeater
cross apply ( Select
startIncrement = dateadd(hour, repeater.increment - 1, roundedStart)
, endInrement = dateadd(hour, repeater.increment, roundedStart)
) as timeIncrements
cross apply (Select
startDateTime = max(startDT)
, endDateTime = min(endDT)
from (values (employee.startDateTime, employee.endDateTime),
(startIncrement, endInrement) ) s (startDT, endDT)
) as final
This seems like a good place to use a recursive CTE:
with cte as (
select employeecode, startdatetime as starthour,
dateadd(hour, datepart(hour, startdatetime) + 1, convert(datetime, convert(date, startdatetime))) as endhour,
enddatetime
from t
union all
select employeecode, endhour,
(case when dateadd(hour, 1, endhour) > enddatetime then enddatetime else dateadd(hour, 1, endhour) end),
enddatetime
from cte
where endhour < enddatetime
)
select *
from cte;
Here is a db<>fiddle.
If you can have more than 100 hours in a period (which seems unlikely because the periods appear to be on the same day), then you need option (maxrecursion 0).

Split datetime range on a specific date into 2 records

Let's give a datetime 2019-04-22 00:00:00.000 (midnight of 22 April 2019)
Now I have a table of records that contains a StartDate and EndDate
ID StartDate EndDate
--------------------------------------------------------------------------
1 2019-04-15 00:00:00.000 2019-04-18 00:00:00.000
2 2019-04-16 00:00:00.000 2019-04-28 00:00:00.000
3 2019-04-23 00:00:00.000 2019-04-25 00:00:00.000
How can I split record with ID = 2 so that I get two records:
record one start date: 2019-04-16 00:00:00.000 and end date: 2019-04-21 23:59:59.000
record two start date: 2019-04-22 00:00:00.000 and end date: 2019-04-28 00:00:00.000
Basically if a range has the start date before 2019-04-22 00:00:00.000 and end date after 2019-04-22 00:00:00.000 then split that record into two records where the end date will be midnight before 2019-04-22 00:00:00.000 for the first record and start date will become 2019-04-22 00:00:00.000 for the second record.
I'd do a UNION ALL, where the first SELECT returns all rows starting before given datetime, and the second SELECT returns all rows ending after that datetime. Something like:
select id, startdate, case when EndDate < '2019-04-22 00:00:00.000' then EndDate
else '2019-04-22 00:00:00.000' end EndDate
from tablename
where startdate < '2019-04-22 00:00:00.000'
UNION ALL
select id, case when startdate > '2019-04-22 00:00:00.000' then startdate
else '2019-04-22 00:00:00.000' end,
EndDate
from tablename
where EndDate > '2019-04-22 00:00:00.000'
The case expressions are there to adjust start or end time for the overlapping rows, those who are splitted into two separate rows.
You can use the following solution:
DECLARE #splitDate DATE = '2019-04-22 00:00:00.000';
SELECT ID, StartDate, (select min(i) from (values (test.EndDate), (#splitDate)) AS T(i)) AS EndDate
FROM table_name WHERE StartDate < #splitDate
UNION ALL
SELECT ID, (select max(i) from (values (test.StartDate), (#splitDate)) AS T(i)) AS StartDate, EndDate
FROM table_name WHERE EndDate > #splitDate
demo on dbfiddle.uk

Query should be changed

I have one query this query display will be based on the day which day the hours is there based on the week it will display.
In this hours table my start date and end date starts from 2014-01-06 2014-01-12 one week..
for eg:
declare #Hours as Table ( EmpId Int, Monday Time, Tuesday Time, Wednesday Time,
Thursday Time, Friday Time, Saturday Time, Sunday Time, StartDate Date, EndDate Date );
insert into #Hours
( EmpId, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, StartDate, EndDate )
values
( 1, '08:00', '00:00', '00:00', '00:00', '00:00', '00:00', '00:00', '2014-01-06 00:00:00.000', '2014-01-12 00:00:00.000' );
select * from #Hours;
with Week as (
-- Build a table of all of the dates in the work week.
select StartDate as WorkDate, EndDate
from #Hours
union all
select DateAdd( day, 1, WorkDate ), EndDate
from Week
where WorkDate < EndDate ),
DaysHours as (
-- Build a table of the hours assigned to each date.
select EmpId,
case DatePart( weekday, W.WorkDate )
when 1 then H.Monday
when 2 then H.Tuesday
when 3 then H.Wednesday
when 4 then H.Thursday
when 5 then H.Friday
when 6 then H.Saturday
when 7 then H.Sunday
end as Hours,
WorkDate as StartDate, WorkDate as EndDate
from Week as W inner join
#Hours as H on H.StartDate <= W.WorkDate and W.WorkDate <= H.EndDate )
-- Output the non-zero hours.
select EmpId, Hours,StartDate as StartDate, EndDate
from DaysHours
where Hours <> Cast( '00:00' as Time );
need the output like this:
EmpId Hours StartDate EndDate
----------- ---------------- ---------- ----------
1 08:00:00.0000000 2014-01-06 2014-01-06
because monday only we have the value.but my output like this
EmpId Hours StartDate EndDate
----------- ---------------- ---------- ----------
1 08:00:00.0000000 2014-01-12 2014-01-12
it should work based on which day the value is there.
Can any body alter in this query itself.
Thanks,
After execution got
EmpId Hours StartDate EndDate
1 08:00:00.0000000 2014-01-06 2014-01-06
MS SQL Server 2008 R2
Try this query:
SELECT x.EmpId, ca.Hours, ca.CurrentDate AS StartDate, ca.CurrentDate AS EndDate
FROM #Hours x
CROSS APPLY
(
SELECT y.Hours, DATEADD(DAY, y.NumOfDays, x.StartDate)
FROM
(
SELECT x.Monday, 0
UNION ALL SELECT x.Tuesday, 1
UNION ALL SELECT x.Wednesday, 2
UNION ALL SELECT x.Thursday, 3
UNION ALL SELECT x.Friday, 4
UNION ALL SELECT x.Saturday, 5
UNION ALL SELECT x.Sunday, 6
) y (Hours, NumOfDays)
WHERE y.Hours <> '00:00:00.0000000'
) ca (Hours, CurrentDate);

How to repeat the values between the two dates?

Using SQL Server 2005
I want to generate the table values between the two dates
Table1
ID Date Intime Outtime
01 12-02-2009 10:00:00 17:00:00
02 13-02-2009 08:00:00 16:00:00
03 14-02-2009 09:00:00 21:00:00
04 15-02-2009
Suppose I want to generate the above table values between the two dates.
For Example,
Given Date: start date - 12-02-2009 to end date - 12-03-2009
Expected Output,
ID Date Intime Outtime
01 12-02-2009 10:00:00 17:00:00
02 13-02-2009 08:00:00 16:00:00
03 14-02-2009 09:00:00 21:00:00
04 15-02-2009
05 16-02-2009 10:00:00 17:00:00
06 17-02-2009 08:00:00 16:00:00
07 18-02-2009 09:00:00 21:00:00
08 19-02-2009
09 20-02-2009 10:00:00 17:00:00
…,
So From table1 we have 4 Rows, so the 4 rows will repeat up to the given end date but id and date should increment, id and date should not repeat.
How to make a SQL Query for this condition?
Need Query Help.
Try this too...
declare #startdate datetime
declare #enddate datetime
set #startdate = '12-02-2009'
set #enddate = '12-05-2009'
;with num_cte as
(
select top(datediff(day,#startdate,#enddate)) ROW_NUMBER() OVER (ORDER BY c.column_id) AS rn
from sys.columns c
)
, generate_date_cte as
(
select 1 as Id,#startdate as newdate
union all
select rn+1 as Id, newdate
from num_cte
cross apply( select DATEADD(day,rn,#startdate) AS newdate) x
)
select * from generate_date_cte
Another solution
declare #startdate datetime
declare #enddate datetime
set #startdate = '12-02-2009'
set #enddate = '12-05-2009'
; with generateCalender_cte as
(
select
1 as Id
,#startdate as newdate
union all
select
c.Id + 1
,DATEADD(day,1,c.newdate)
from generateCalender_cte c
where c.newdate <#enddate
)
select * from generateCalender_cte
Try this
declare #startdate datetime
declare #enddate datetime
set #startdate = '12-02-2009'
set #enddate = '12-05-2009'
;with generateCalender_cte as
(
select
1 as Id
,#startdate DateValue
union all
select
c.Id + 1
,DateValue + 1
from generateCalender_cte c
where DateValue + 1 <= #enddate
)
select * from generateCalender_cte