How to calculate overlaping time and length of conflict - sql

Employee table has four columns. Employee, Start, End, Diff. Diff column is the duration and calculated as End - Start.
I want to find the conflict between the Start and End time range.
For instance, Employee A has three rows:
First rows Start time is 01:02 and end time is 01:05 but second row start time is 01:03 which is a conflict in the first row data.
Sample Data:
employee StartDate EndDate Start End Diff
A 04/08/2019 04/08/2019 01:02:00 01:05:00 3
A 04/08/2019 04/08/2019 01:03:00 01:08:00 5
A 04/08/2019 04/08/2019 01:014:00 01:21:00 7
B 04/08/2019 04/08/2019 02:00:00 02:17:00 17
I want to only select the specific start and end time for employee A that has an overlap in their start and end time and want to calculate total length of conflict in a new column using t-sql. i'm a newbie and need help. please anyone?
SELECT TOP (100) a.ccx_employeename AS employee
,CONVERT(Date,[a].[ccx_starttime]) AS [Start Date],CONVERT(Date,[a].[ccx_endtime]) AS [End Date], CONVERT(time (0), a.ccx_starttime) AS StartTime
, CONVERT(time (0), a.ccx_endtime) AS EndTime
, CONVERT (time(0), (a.ccx_endtime - a.ccx_starttime)) AS Duration
FROM ccp_sim_MSCRM.dbo.Filteredccx_Recorded_Service as a
where CONVERT(time (0), a.ccx_starttime) BETWEEN CONVERT(time (0), a.ccx_starttime) And CONVERT(time (0), a.ccx_endtime)
As first and second rows has conflict I want to show that two rows. As well as conflict duration is 2 minutes in this example. First row end time is 01:05 but second rows start time is 01:03 so conflict duration is 01:05 - 01:03 = 2 minutes
Desired Output
employee StartDate EndDate Start End Diff
A 04/08/2019 04/08/2019 01:02:00 01:05:00 3
A 04/08/2019 04/08/2019 01:03:00 01:08:00 5
duration of conflict : 2 mins

I would join the table over itself, though maybe not the most effective :
SELECT
e1.employee,
e1.Start as firstStart,
e1.End as firstEnd,
e2.Start as secondStart,
e2.End as secondEnd,
e1.End - e2.Start as conflictDuration
FROM
Employee as e1 inner join
Employee as e2 on (
e1.employee = e2.employee and
e2.Start < e1.End and
e2.End > e1.Start
)

There are a few parts to your question:
finding the conflicting rows
calculating the conflicting time
output in your desired format
The solution below only covers the first two parts and assumes a combined date and time field.
I have added some unique key to deduplicate the results and "sort" the rows for comparison. In the code below, it is "id".
declare #t table (id int identity,employee char(1), StartDateTime smalldatetime, EndDateTime smalldatetime, diff as DATEDIFF(minute,StartDateTime,EndDateTime))
insert into #t values('A','2019-04-08 01:02','2019-04-08 01:05')
insert into #t values('A','2019-04-08 01:03','2019-04-08 01:08')
insert into #t values('A','2019-04-08 01:14','2019-04-08 01:21')
insert into #t values('B','2019-04-08 02:00','2019-04-08 02:17')
SELECT T1.employee, T1.StartDateTime, T1.EndDateTime, T2.StartDateTime, T2.EndDateTime
, (T1.diff + T2.diff)
- DATEDIFF(minute, CASE WHEN T1.StartDateTime < T2.StartDateTime THEN T1.StartDateTime ELSE T2.StartDateTime END -- MIN(Start)
, CASE WHEN T1.EndDateTime > T2.EndDateTime THEN T1.EndDateTime ELSE T2.EndDateTime END) -- MAX(End)
AS "duration of conflict"
FROM #t AS T1
JOIN #t AS T2
ON T2.employee = T1.employee
AND T2.id > T1.id -- Each only once
AND T2.StartDateTime < T1.EndDateTime
AND T2.EndDateTime > T1.StartDateTime

This feels like the perfect place to use the LEAD/LAG functions to me. Combine that with some sub queries and IIF statements and you can calculate the results you're looking for.
Example:
DECLARE #Employee TABLE
(
Employee VARCHAR(1),
startDate DATE,
endDate DATE,
[start] TIME,
[end] TIME,
diff AS DATEDIFF(MINUTE,[start],[end])
)
INSERT INTO #Employee (Employee, startDate, endDate, start, [end])
VALUES
('A',CAST('2019-04-08' AS DATE),CAST('2019-04-08' AS DATE),'01:02:00','01:05:00'),
('A',CAST('2019-04-08' AS DATE),CAST('2019-04-08' AS DATE),'01:03:00','01:08:00'),
('A',CAST('2019-04-08' AS DATE),CAST('2019-04-08' AS DATE),'01:14:00','01:21:00'),
('B',CAST('2019-04-08' AS DATE),CAST('2019-04-08' AS DATE),'02:00:00','02:17:00')
SELECT
Employee.Employee,
Employee.startDate,
Employee.endDate,
Employee.start,
Employee.[end],
diff,
(IIF(ISNULL(lagConflict,0)>0,ISNULL(lagConflict,0),0)+IIF(ISNULL(Employee.leadConflict,0)>0,ISNULL(Employee.leadConflict,0),0)) AS conflict
FROM
(
SELECT
Employee,
startDate,
endDate,
start,
[end],
diff,
DATEDIFF
(
MINUTE,
[start],
LAG([end],1)
OVER
(
PARTITION BY
Employee,
startDate,
endDate
ORDER BY
[start],
[end]
)
) AS lagConflict,
DATEDIFF
(
MINUTE,
[end],
LEAD([start],1)
OVER
(
PARTITION BY
Employee,
startDate,
endDate
ORDER BY
[start],
[end]
)
)*-1 AS leadConflict
FROM
#Employee
) AS Employee
WHERE
Employee.leadConflict > 0
OR Employee.lagConflict > 0;
Microsoft SQL Docs: LAG]1

Related

Determine time gaps in SQL

I'm trying to self learn the lag and lead functions and thought I would try it with the following report but I'm not having much luck. My goal is to take some on-call start and stop times for a department and create a report detailing the time gaps for the day that there was no coverage. The department is assumed to have 24 hour coverage, 7 days a week. Is the only way to handle this to join it to a datetime table that has the date and every minute available? Any suggestions would be greatly appreciated.
The expected outcome for the data below would be:
on 09/01/2020 dept 3042300031 had a time gap from 20:59 to 21:00 and a time gap from 22:59 to 23:59
on 09/02/2020 dept 3042300031 had a time gap from 00:00 to 00:05 and a time gap from 20:59 to 22:00 and a time gap from 22:50 to 23:59
on 09/03/2020 dept 3042300031 had a time gap from 00:00 to 23:59
on 09/04/2020 dept 3042300031 had a time gap from 20:59 to 23:59
IF OBJECT_ID('tempdb..#report') IS NOT NULL DROP TABLE #report
GO
CREATE TABLE #report (
Contact_Date date
,Line int
,Start_Instant_dttm smalldatetime
,End_Instant_dttm datetime
,Asgn_to_Role int
,Asgn_to_Team bigint
);
INSERT INTO #report
SELECT
'9/1/2020',1,'9/1/2020 00:00','9/1/2020 20:59',270,3042300031
UNION
SELECT
'9/1/2020',2,'9/1/2020 21:00','9/1/2020 22:59',270,3042300031
UNION
SELECT
'9/2/2020',1,'9/2/2020 00:05','9/2/2020 20:59',270,3042300031
UNION
SELECT
'9/2/2020',2,'9/2/2020 22:00','9/2/2020 22:59',270,3042300031
UNION
SELECT
'9/4/2020',1,'9/4/2020 00:00','9/4/2020 20:59',270,3042300031;
Finding the gaps is simple:
with cte as
(
select Asgn_to_Team, Start_Instant_dttm, End_Instant_dttm
,lag(End_Instant_dttm)
over (partition by Asgn_to_Team
order by Start_Instant_dttm) as prev_end
from #report
)
select Asgn_to_Team, prev_end as gap_start, Start_Instant_dttm as gap_end
from cte
where prev_end < Start_Instant_dttm
But splitting them into days is much harder, you need to join to a calendar table:
with cte as
(
select Asgn_to_Team, Start_Instant_dttm, End_Instant_dttm
,lag(End_Instant_dttm)
over (partition by Asgn_to_Team
order by Start_Instant_dttm) as prev_end
from #report
)
select Asgn_to_Team,
case when prev_end > cast(cal.cal_date as datetime)
then prev_end
else cast(cal.cal_date as datetime)
end as gap_start,
case when Start_Instant_dttm < cast(dateadd(day, 1, cal.cal_date) as datetime)
then Start_Instant_dttm
else cast(dateadd(day, 1, cal.cal_date) as datetime)
end as gap_end
from cte join cal -- one row for each date covered
on cast(cal.cal_date as datetime) <= Start_Instant_dttm
and cast(dateadd(day, 1, cal.cal_date) as datetime) > prev_end
where prev_end < Start_Instant_dttm
Hopefully I got the >/< right, see fiddle

Finding Time overlaps for two differen timlines

I am using SQL SERVER. I have two tables table1 and table2. Both of them store time intervals, for simplicity just say both has two datetime2 column, column names are S1 and S2 for table 1, T1 and T2 for table2, for each row S1 is Greater than S2 , exactly for the table two. I want to calculate the value of intervals between S2 and S1(like a timeline) and minus it from overlap of T1 and T2 over S1 and S2. I tried this but can't go further than first part of calculation
DECLARE #x float
SET x=0
SELECT SUM(S1-S2)-x from table1
(set x =(SELECT (T1-T2) FROM table2
WHERE T1>=S1 and T2<=S2));
Example:
S2= 10/25/2012 ; S1= 11/30/2012;
assume that we have three rows in table 2
T2=10/20/2012 , T1=10/28/2012
T2=11/4/2012 , T1=11/8/2012
T2=11/22/2012 , T1=11/30/2012
what I want is to find total minutes between S1 and S2 except the minutes that overlapped with second table T1 and T2 intervals. My query works for the second row in second table when the whole interval between T1 and T2 is in the interval of S1 and S2.
This is somehow complicated hope this example helps
Query works fine but i can not calculate the overlap value with the query when one of the T1 or T2 are in the S1 and S2 interval. Should i run multiple queries? What are the parallels here?
I'm using SQL Server 2008 for this example.
This solution assumes that all intervals in table T do not overlap with each other.
The following articles explain interval algebra in detail and I think they are a very good read. They have nice diagrams as well.
Comparing date ranges
http://salman-w.blogspot.com.au/2012/06/sql-query-overlapping-date-ranges.html
http://www.ics.uci.edu/~alspaugh/cls/shr/allen.html
http://stewashton.wordpress.com/2014/03/11/sql-for-date-ranges-gaps-and-overlaps/
Create tables with sample data
I named the columns in a less confusing manner than in the original question. I've added few extra intervals that do not overlap to illustrate that proposed solution filters them out.
DECLARE #TableS TABLE (ID int IDENTITY(1,1), DateFromS date, DateToS date);
DECLARE #TableT TABLE (ID int IDENTITY(1,1), DateFromT date, DateToT date);
INSERT INTO #TableS (DateFromS, DateToS) VALUES ('2012-10-25', '2012-11-30');
INSERT INTO #TableS (DateFromS, DateToS) VALUES ('2015-10-25', '2015-11-30');
INSERT INTO #TableT (DateFromT, DateToT) VALUES ('2012-10-20', '2012-10-28');
INSERT INTO #TableT (DateFromT, DateToT) VALUES ('2012-11-04', '2012-11-08');
INSERT INTO #TableT (DateFromT, DateToT) VALUES ('2012-11-22', '2012-11-30');
INSERT INTO #TableT (DateFromT, DateToT) VALUES ('2010-11-22', '2010-11-30');
INSERT INTO #TableT (DateFromT, DateToT) VALUES ('2020-11-22', '2020-11-30');
Find overlapping intervals
I assume that we want to do these calculations for each row in the table S and for each row in table T. If this is not the case, you should join tables with some extra condition.
In this example I work only with days precision, not minutes, and I assume that start and end dates are inclusive, i.e. duration between 01/01/2000 and 01/01/2000 is one day. It should be fairly straightforward to extend this to minute precision.
SELECT *
,ISNULL(1+DATEDIFF(day, MaxDateFrom.DateFrom, MinDateTo.DateTo), 0) AS OverlappedDays
FROM
#TableS AS TS
LEFT JOIN #TableT AS TT ON TS.DateFromS <= TT.DateToT AND TS.DateToS >= TT.DateFromT
-- all periods in TS, which overlap with periods in TT
--(StartA <= EndB) and (EndA >= StartB)
CROSS APPLY
(
SELECT CASE WHEN TS.DateFromS > TT.DateFromT THEN TS.DateFromS ELSE TT.DateFromT END AS DateFrom
) AS MaxDateFrom
CROSS APPLY
(
SELECT CASE WHEN TS.DateToS < TT.DateToT THEN TS.DateToS ELSE TT.DateToT END AS DateTo
) AS MinDateTo
The condition in LEFT JOIN leaves only overlapping intervals. To calculate the duration of the overlapping interval I use two CROSS APPLYs. This is the result set of this intermediary query:
ID DateFromS DateToS ID DateFromT DateToT DateFrom DateTo OverlappedDays
1 2012-10-25 2012-11-30 1 2012-10-20 2012-10-28 2012-10-25 2012-10-28 4
1 2012-10-25 2012-11-30 2 2012-11-04 2012-11-08 2012-11-04 2012-11-08 5
1 2012-10-25 2012-11-30 3 2012-11-22 2012-11-30 2012-11-22 2012-11-30 9
2 2015-10-25 2015-11-30 NULL NULL NULL NULL NULL 0
Note, that the last row corresponds to the case when an interval in table S doesn't overlap with any intervals from table T.
Calculate durations
Now all we need is to sum up the duration of overlapping intervals T for each original row in table S and subtract it from the duration of the interval S.
SELECT
TS.ID
,TS.DateFromS
,TS.DateToS
,1+DATEDIFF(day, TS.DateFromS, TS.DateToS) AS DurationS
,ISNULL(SUM(1+DATEDIFF(day, MaxDateFrom.DateFrom, MinDateTo.DateTo)), 0) AS DurationOverlapped
,1+DATEDIFF(day, TS.DateFromS, TS.DateToS)
- ISNULL(SUM(1+DATEDIFF(day, MaxDateFrom.DateFrom, MinDateTo.DateTo)), 0) AS FinalDuration
FROM
#TableS AS TS
LEFT JOIN #TableT AS TT ON TS.DateFromS <= TT.DateToT AND TS.DateToS >= TT.DateFromT
CROSS APPLY
(
SELECT CASE WHEN TS.DateFromS > TT.DateFromT THEN TS.DateFromS ELSE TT.DateFromT END AS DateFrom
) AS MaxDateFrom
CROSS APPLY
(
SELECT CASE WHEN TS.DateToS < TT.DateToT THEN TS.DateToS ELSE TT.DateToT END AS DateTo
) AS MinDateTo
GROUP BY TS.ID, TS.DateFromS, TS.DateToS
This is the result set:
ID DateFromS DateToS DurationS DurationOverlapped FinalDuration
1 2012-10-25 2012-11-30 37 18 19
2 2015-10-25 2015-11-30 37 0 37
You are interested in the FinalDuration value, which is 19 for your example and 37 for the second interval that I added for this example.
You can add more intervals to the sample data to play with the queries and see how they work.
This solution assumes that all intervals in table T do not overlap with each other.

To club the rows for week days

I have data like below:
StartDate EndDate Duration
----------
41890 41892 3
41898 41900 3
41906 41907 2
41910 41910 1
StartDate and EndDate are respective ID values for any dates from calendar. I want to calculate the sum of duration for consecutive days. Here I want to include the days which are weekends. E.g. in the above data, let's say 41908 and 41909 are weekends, then my required result set should look like below.
I already have another proc that can return me the next working day, i.e. if I pass 41907 or 41908 or 41909 as DateID in that proc, it will return 41910 as the next working day. Basically I want to check if the DateID returned by my proc when I pass the above EndDateID is same as the next StartDateID from above data, then both the rows should be clubbed. Below is the data I want to get.
ID StartDate EndDate Duration
----------
278457 41890 41892 3
278457 41898 41900 3
278457 41906 41910 3
Please let me know in case the requirement is not clear, I can explain further.
My Date Table is like below:
DateId Date Day
----------
41906 09-04-2014 Thursday
41907 09-05-2014 Friday
41908 09-06-2014 Saturdat
41909 09-07-2014 Sunday
41910 09-08-2014 Monday
Here is the SQL Code for setup:
CREATE TABLE Table1
(
StartDate INT,
EndDate INT,
LeaveDuration INT
)
INSERT INTO Table1
VALUES(41890, 41892, 3),
(41898, 41900, 3),
(41906, 41907, 3),
(41910, 41910, 1)
CREATE TABLE DateTable
(
DateID INT,
Date DATETIME,
Day VARCHAR(20)
)
INSERT INTO DateTable
VALUES(41907, '09-05-2014', 'Friday'),
(41908, '09-06-2014', 'Saturday'),
(41909, '09-07-2014', 'Sunday'),
(41910, '09-08-2014', 'Monday'),
(41911, '09-09-2014', 'Tuesday')
This is rather complicated. Here is an approach using window functions.
First, use the date table to enumerate the dates without weekends (you can also take out holidays if you want). Then, expand the periods into one day per row, by using a non-equijoin.
You can then use a trick to identify sequential days. This trick is to generate a sequential number for each id and subtract it from the sequential number for the dates. This is a constant for sequential days. The final step is simply an aggregation.
The resulting query is something like this:
with d as (
select d.*, row_number() over (order by date) as seqnum
from dates d
where day not in ('Saturday', 'Sunday')
)
select t.id, min(t.date) as startdate, max(t.date) as enddate, sum(duration)
from (select t.*, ds.seqnum, ds.date,
(d.seqnum - row_number() over (partition by id order by ds.date) ) as grp
from table t join
d ds
on ds.date between t.startdate and t.enddate
) t
group by t.id, grp;
EDIT:
The following is the version on this SQL Fiddle:
with d as (
select d.*, row_number() over (order by date) as seqnum
from datetable d
where day not in ('Saturday', 'Sunday')
)
select t.id, min(t.date) as startdate, max(t.date) as enddate, sum(duration)
from (select t.*, ds.seqnum, ds.date,
(ds.seqnum - row_number() over (partition by id order by ds.date) ) as grp
from (select t.*, 'abc' as id from table1 t) t join
d ds
on ds.dateid between t.startdate and t.enddate
) t
group by grp;
I believe this is working, but the date table doesn't have all the dates in it.

SQL - How to ignore seconds and round down minutes in DateTime data type

At work we did a project that required a team to count students 8 times a day over 5 days at specific time periods. They are, as follows :-
09:00, 10:00, 11:00, 13:15, 14:15, 14:50, 15:50, 16:20.
Now, the data collected was put directly into a database via a web app. The problem is that database recorded each record using the standard YYYY-MM-DD HH:MM:SS.MIL, but if I were to order the records by date and then by student count it would cause the following problem;
e.g.:-
if the students counted in a room was 5 at 09:00:12, but another room had a count of 0 at 09:02:20 and I did the following:
select student_count, audit_date
from table_name
order by audit_date, student_count;
The query will return:
5 09:00:12
0 09:02:20
but I want:
0 09:00:00
5 09:00:00
because we're looking for the number of students in each room for the period 09:00, but unfortunately to collect the data it required us to do so within that hour and obviously the database will pick up on that accuracy. Furthermore, this issue becomes more problematic when it gets to the periods 14:15 and 14:50, where we will need to be able to distinguish between the two periods.
Is there a way to ignore the seconds part of the DateTime, and the round the minutes down to the nearest ten minute?
I'm using SQL Server Management Studio 2012. If none of this made sense, I'm sorry!
You may want some sort of Period table to store your segments. Then you can use that to join to your counts table.
CREATE TABLE [Periods]
( -- maybe [id] INT,
[start_time] TIME,
[end_time] TIME
);
INSERT INTO [Periods]
VALUES ('09:00','10:00'),
('10:00','11:00'),
('11:00','13:15'),
('13:15','14:15'),
('14:15','14:50'),
('14:50','15:50'),
('15:50','16:20'),
('16:20','17:00')
SELECT
student_count, [start_time]
FROM table_name A
INNER JOIN [Periods] B
ON CAST(A.[audit_date] AS TIME) >= B.[start_time]
AND CAST(A.[audit_date] AS TIME) < B.[end_time]
You can use the DATEADDand DATEPARTfunctions to accomplish this together with a CASEexpression. If you want more precise cutoffs between the .14and .50periods you can easily adjust the case statement and also if you want to minutes to be .10or.15
-- some test data
declare #t table (the_time time)
insert #t values ('09:00:12')
insert #t values ('14:16:12')
insert #t values ('09:02:12')
insert #t values ('14:22:12')
insert #t values ('15:49:12')
insert #t values ('15:50:08')
select
the_time,
case
when datepart(minute,the_time) < 15 then
dateadd(second, -datepart(second,the_time),dateadd(minute, -datepart(minute,the_time),the_time))
when datepart(minute,the_time) >= 15 and datepart(minute,the_time) < 50 then
dateadd(second, -datepart(second,the_time),dateadd(minute, -datepart(minute,the_time)+10,the_time))
else
dateadd(second, -datepart(second,the_time),dateadd(minute, -datepart(minute,the_time)+50,the_time))
end as WithoutSeconds
from #t
Results:
the_time WithoutSeconds
---------------- ----------------
09:00:12.0000000 09:00:00.0000000
14:16:12.0000000 14:10:00.0000000
09:02:12.0000000 09:00:00.0000000
14:22:12.0000000 14:10:00.0000000
15:49:12.0000000 15:10:00.0000000
15:50:08.0000000 15:50:00.0000000
Try this:
SELECT
CAST(
DATEADD(SECOND, - (CONVERT(INT, RIGHT(CONVERT(CHAR(2),
DATEPART(MINUTE, GETDATE())),1))*60) - (DATEPART(SECOND,GETDATE())), GETDATE())
AS SMALLDATETIME);
You can try ORDER BY this formula
DATEADD(minute, floor((DATEDIFF(minute, '20000101', audit_date) + 5)/10)*10, '20000101')
e.g.
WITH tbl AS(
SELECT * FROM ( VALUES (5,'2014-03-28 09:00:09.793'),(0,'2014-03-28 09:02:20.123')) a (student_count, audit_date)
)
SELECT *,DATEADD(minute, floor((DATEDIFF(minute, '20000101', audit_date) + 5)/10)*10, '20000101') as ORDER_DT
FROM tbl
ORDER BY ORDER_DT,student_count
SQL Fiddle

trying to find the maximum number of occurrences over time T-SQL

I have data recording the StartDateTime and EndDateTime (both DATETIME2) of a process for all of the year 2013.
My task is to find the maximum amount of times the process was being ran at any specific time throughout the year.
I have wrote some code to check every minute/second how many processes were running at the specific time, but this takes a very long time and would be impossible to let it run for the whole year.
Here is the code (in this case check every minute for the date 25/10/2013)
CREATE TABLE dbo.#Hit
(
ID INT IDENTITY (1,1) PRIMARY KEY,
Moment DATETIME2,
COUNT INT
)
DECLARE #moment DATETIME2
SET #moment = '2013-10-24 00:00:00'
WHILE #moment < '2013-10-25'
BEGIN
INSERT INTO #Hit ( Moment, COUNT )
SELECT #moment, COUNT(*)
FROM dbo.tblProcessTimeLog
WHERE ProcessFK IN (25)
AND #moment BETWEEN StartDateTime AND EndDateTime
AND DelInd = 0
PRINT #moment
SET #moment = DATEADD(MINute,1,#moment)
END
SELECT * FROM #Hit
ORDER BY COUNT DESC
Can anyone think how i could get a similar result (I just need the maximum amount of processes being run at any given time), but for all year?
Thanks
DECLARE #d DATETIME = '20130101'; -- the first day of the year you care about
;WITH m(m) AS
( -- all the minutes in a day
SELECT TOP (1440) ROW_NUMBER() OVER (ORDER BY number) - 1
FROM master..spt_values
),
d(d) AS
( -- all the days in *that* year (accounts for leap years vs. hard-coding 365)
SELECT TOP (DATEDIFF(DAY, #d, DATEADD(YEAR, 1, #d))) DATEADD(DAY, number, #d)
FROM master..spt_values WHERE type = N'P' ORDER BY number
),
x AS
( -- all the minutes in *that* year
SELECT moment = DATEADD(MINUTE, m.m, d.d) FROM m CROSS JOIN d
)
SELECT TOP (1) WITH TIES -- in case more than one at the top
x.moment, [COUNT] = COUNT(l.ProcessFK)
FROM x
INNER JOIN dbo.tblProcessTimeLog AS l
ON x.moment >= l.StartDateTime
AND x.moment <= l.EndDateTime
WHERE l.ProcessFK = 25 AND l.DelInd = 0
GROUP BY x.moment
ORDER BY [COUNT] DESC;
See this post for why I don't think you should use BETWEEN for range queries, even in cases where it does semantically do what you want.
Create a table T whose rows represent some time segments.
This table could well be a temporary table (depending on your case).
Say:
row 1 - [from=00:00:00, to=00:00:01)
row 2 - [from=00:00:01, to=00:00:02)
row 3 - [from=00:00:02, to=00:00:03)
and so on.
Then just join from your main table
(tblProcessTimeLog, I think) to this table
based on the datetime values recorded in
tblProcessTimeLog.
A year has just about half million minutes
so it is not that many rows to store in T.
I recently pulled some code from SO trying to solve the 'island and gaps' problem, and the algorithm for that should help you solve your problem.
The idea is that you want to find the point in time that has the most started processes, much like figuring out the deepest nesting of parenthesis in an expression:
( ( ( ) ( ( ( (deepest here, 6)))))
This sql will produce this result for you (I included a temp table with sample data):
/*
CREATE TABLE #tblProcessTimeLog
(
StartDateTime DATETIME2,
EndDateTime DATETIME2
)
-- delete from #tblProcessTimeLog
INSERT INTO #tblProcessTimeLog (StartDateTime, EndDateTime)
Values ('1/1/2012', '1/6/2012'),
('1/2/2012', '1/6/2012'),
('1/3/2012', '1/6/2012'),
('1/4/2012', '1/6/2012'),
('1/5/2012', '1/7/2012'),
('1/6/2012', '1/8/2012'),
('1/6/2012', '1/10/2012'),
('1/6/2012', '1/11/2012'),
('1/10/2012', '1/12/2012'),
('1/15/2012', '1/16/2012')
;
*/
with cteProcessGroups (EventDate, GroupId) as
(
select EVENT_DATE, (E.START_ORDINAL - E.OVERALL_ORDINAL) GROUP_ID
FROM
(
select EVENT_DATE, EVENT_TYPE,
MAX(START_ORDINAL) OVER (ORDER BY EVENT_DATE, EVENT_TYPE ROWS UNBOUNDED PRECEDING) as START_ORDINAL,
ROW_NUMBER() OVER (ORDER BY EVENT_DATE, EVENT_TYPE) AS OVERALL_ORDINAL
from
(
Select StartDateTime AS EVENT_DATE, 1 as EVENT_TYPE, ROW_NUMBER() OVER (ORDER BY StartDateTime) as START_ORDINAL
from #tblProcessTimeLog
UNION ALL
select EndDateTime, 0 as EVENT_TYPE, NULL
FROM #tblProcessTimeLog
) RAWDATA
) E
)
select Max(EventDate) as EventDate, count(GroupId) as OpenProcesses
from cteProcessGroups
group by (GroupId)
order by COUNT(GroupId) desc
Results:
EventDate OpenProcesses
2012-01-05 00:00:00.0000000 5
2012-01-06 00:00:00.0000000 4
2012-01-15 00:00:00.0000000 2
2012-01-10 00:00:00.0000000 2
2012-01-08 00:00:00.0000000 1
2012-01-07 00:00:00.0000000 1
2012-01-11 00:00:00.0000000 1
2012-01-06 00:00:00.0000000 1
2012-01-06 00:00:00.0000000 1
2012-01-06 00:00:00.0000000 1
2012-01-16 00:00:00.0000000 1
Note that the 'in-between' rows don't give anything meaningful. Basically this output is only tuned to tell you when the most activity was. Looking at the other rows in the out put, there wasn't just 1 process running on 1/8 (there was actually 3). But the way this code works is that by grouping the processes that are concurrent together in a group, you can count the number of simultaneous processes. The date returned is when the max concurrent processes began. It doesn't tell you how long they were going on for, but you can solve that with an additional query. (once you know the date the most was ocurring, you can find out the specific process IDs by using a BETWEEN statement on the date.)
Hope this helps.