如何计算时间间隔?
我有一个问题,我解决了,但我写了一个很长的程序,我不能确定它涵盖了所有可能的情况。
问题:
如果我有一个主间隔时间 ( From A to B
)和次要间隔时间 (很多或没有)
(`From X to Y AND From X` to Y` AND X`` to Y`` AND ....`)
我希望在有效和最少数量的条件(SQL服务器过程和C#方法)中以几分钟的时间间隔将我的主间隔时间(AB)的所有部分从辅助时间间隔中除去 ?
例如:如果我的主要间隔从02:00 to 10:30
并且说一个次要间隔从04:00 to 08:00
现在我想要这个结果: ((04:00 - 02:00) + (10:30 -08:00))* 60
图表示例:
在第一种情况下,结果将是:
((XA) + (BY)) * 60
当我有许多次要时期时会更复杂。
注意:
可能是仅当我必须将主周期[A,B]与至多两个并行的次要间隔集合的UNION进行比较时发生的次要间隔之间的重叠。第一组必须仅包含一个次要间隔而第二组必须包含set包含(很多或没有)次要区间。例如,在比较[A,B]
和( 2,5
)的图中,第一组(2)
由一个次级区间组成,第二组(5)
由三个次要间隔。 这是最糟糕的情况,我需要处理。
例如 :
如果我的主要间隔是[15:00,19:40]
并且我有两组次要间隔。根据我的规则,这些集合中的至少一个应该由一个次要间隔组成。 说第一组是[11:00 ,16:00]
,第二组是说两个次要区间[10:00,15:00],[16:30,17:45]
现在我想要的结果(16:30 -16:00) +(19:40 -17:45)
根据评论:
我的桌子是这样的:
第一个表包含次要期间,特定员工在同一日期最多有两组次要期间。 第一组在工作日中仅包含一个次要句点(W)
[work_st,work_end]
,如果该日是周末[E]
,则该组将为空,在这种情况下,次要句点之间不重叠。 并且第二组可能包含同一日期[check_in,check_out]
中的许多次要期间,因为员工可能在同一天多次check_in_out。
emp_num day_date work_st work_end check_in check_out day_state 547 2015-4-1 08:00 16:00 07:45 12:10 W 547 2015-4-1 08:00 16:00 12:45 17:24 W 547 2015-4-2 00:00 00:00 07:11 13:11 E
第二个表包含主要期间[A,B]
,这是该员工当天的一个期间(一个记录)
emp_num day_date mission_in mission_out 547 2015-4-1 15:00 21:30 547 2015-4-2 8:00 14:00
在前面的示例中,如果我有所需的过程或方法,则此过程应采用两个参数:
- 日期
- emp_num
在前面的例子中它应该是这样的('2015-4-1' ,547)
根据我的解释:
-
第二个表中的主要时期(任务期)
[A,B]
:该员工在此日期应该只有一个时期[15:00,21:30]
-
该员工的通过日期
('2015-4-1')
的次要期间是来自第一个表的两组次要期间(最差情况)第一组应该只包含一个次要周期(或零周期)
[08:00,16:00]
第二组可以包含许多次要周期(或零周期)[07:45,12:10]
,[12:45,17:24]
输出应该是[17:24,21:30]转换为分钟
注意
所有day_date,mission_in,mission_out,work_st,work_end,check_in,check_out
都是datetime
字段,但我只是在示例中放置时间以简化,我想忽略除day_date
之外的日期部分,因为它是我另外计算的日期到emp_num
。
我已经使用您的数据示例更新了我的答案,并且我正在为使用图表中的案例2和5的员工248添加另一个示例。
--load example data for emply 547 select CONVERT(int, 547) emp_num, Convert(datetime, '2015-4-1') day_date, Convert(datetime, '2015-4-1 08:00') work_st, Convert(datetime, '2015-4-1 16:00') work_end, Convert(datetime, '2015-4-1 07:45') check_in, Convert(datetime, '2015-4-1 12:10') check_out, 'W' day_state into #SecondaryIntervals insert into #SecondaryIntervals select 547, '2015-4-1', '2015-4-1 08:00', '2015-4-1 16:00', '2015-4-1 12:45', '2015-4-1 17:24', 'W' insert into #SecondaryIntervals select 547, '2015-4-2', '2015-4-2 00:00', '2015-4-2 00:00', '2015-4-2 07:11', '2015-4-2 13:11', 'E' select CONVERT(int, 547) emp_num, Convert(datetime, '2015-4-1') day_date, Convert(datetime, '2015-4-1 15:00') mission_in, Convert(datetime, '2015-4-1 21:30') mission_out into #MainIntervals insert into #MainIntervals select 547, '2015-4-2', '2015-4-2 8:00', '2015-4-2 14:00' --load more example data for an employee 548 with overlapping secondary intervals insert into #SecondaryIntervals select 548, '2015-4-1', '2015-4-1 06:00', '2015-4-1 11:00', '2015-4-1 9:00', '2015-4-1 10:00', 'W' insert into #SecondaryIntervals select 548, '2015-4-1', '2015-4-1 06:00', '2015-4-1 11:00', '2015-4-1 10:30', '2015-4-1 12:30', 'W' insert into #SecondaryIntervals select 548, '2015-4-1', '2015-4-1 06:00', '2015-4-1 11:00', '2015-4-1 13:15', '2015-4-1 16:00', 'W' insert into #MainIntervals select 548, '2015-4-1', '2015-4-1 8:00', '2015-4-1 14:00' --Populate your Offline table with the intervals in #SecondaryIntervals select ROW_NUMBER() over (Order by emp_num, day_date, StartDateTime, EndDateTime) Rownum, emp_num, day_date, StartDateTime, EndDateTime into #Offline from (select emp_num, day_date, work_st StartDateTime, work_end EndDateTime from #SecondaryIntervals where day_state = 'W' Group by emp_num, day_date, work_st, work_end union select emp_num, day_date, check_in StartDateTime, check_out EndDateTime from #SecondaryIntervals Group by emp_num, day_date, check_in, check_out ) SecondaryIntervals --Populate your Online table select ROW_NUMBER() over (Order by emp_num, day_date, mission_in, mission_out) Rownum, emp_num, day_date, mission_in StartDateTime, mission_out EndDateTime into #Online from #MainIntervals group by emp_num, day_date, mission_in, mission_out ------------------------------- --find overlaping offline times ------------------------------- declare @Finished as tinyint set @Finished = 0 while @Finished = 0 Begin update #Offline set #Offline.EndDateTime = OverlapEndDates.EndDateTime from #Offline join ( select #Offline.Rownum, MAX(Overlap.EndDateTime) EndDateTime from #Offline join #Offline Overlap on #Offline.emp_num = Overlap.emp_num and #Offline.day_date = Overlap.day_date and Overlap.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Offline.Rownum <= Overlap.Rownum group by #Offline.Rownum ) OverlapEndDates on #Offline.Rownum = OverlapEndDates.Rownum --Remove Online times completely inside of online times delete #Offline from #Offline join #Offline Overlap on #Offline.emp_num = Overlap.emp_num and #Offline.day_date = Overlap.day_date and #Offline.StartDateTime between Overlap.StartDateTime and Overlap.EndDateTime and #Offline.EndDateTime between Overlap.StartDateTime and Overlap.EndDateTime and #Offline.Rownum > Overlap.Rownum --LOOK IF THERE ARE ANY MORE CHAINS LEFT IF NOT EXISTS( select #Offline.Rownum, MAX(Overlap.EndDateTime) EndDateTime from #Offline join #Offline Overlap on #Offline.emp_num = Overlap.emp_num and #Offline.day_date = Overlap.day_date and Overlap.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Offline.Rownum < Overlap.Rownum group by #Offline.Rownum ) SET @Finished = 1 END ------------------------------- --Modify Online times with offline ranges ------------------------------- --delete any Online times completely inside offline range delete #Online from #Online join #Offline on #Online.emp_num = #Offline.emp_num and #Online.day_date = #Offline.day_date and #Online.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Online.EndDateTime between #Offline.StartDateTime and #Offline.EndDateTime --Find Online Times with offline range at the beginning update #Online set #Online.StartDateTime = #Offline.EndDateTime from #Online join #Offline on #Online.emp_num = #Offline.emp_num and #Online.day_date = #Offline.day_date and #Online.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Online.EndDateTime >= #Offline.EndDateTime --Find Online Times with offline range at the end update #Online set #Online.EndDateTime = #Offline.StartDateTime from #Online join #Offline on #Online.emp_num = #Offline.emp_num and #Online.day_date = #Offline.day_date and #Online.StartDateTime <= #Offline.StartDateTime and #Online.EndDateTime between #Offline.StartDateTime and #Offline.EndDateTime --Find Online Times with offline range punched in the middle select #Online.Rownum, #Offline.Rownum OfflineRow, #Offline.StartDateTime, #Offline.EndDateTime, ROW_NUMBER() over (Partition by #Online.Rownum order by #Offline.Rownum Desc) OfflineHoleNumber into #OfflineHoles from #Online join #Offline on #Online.emp_num = #Offline.emp_num and #Online.day_date = #Offline.day_date and #Offline.StartDateTime between #Online.StartDateTime and #Online.EndDateTime and #Offline.EndDateTime between #Online.StartDateTime and #Online.EndDateTime declare @HoleNumber as integer select @HoleNumber = isnull(MAX(OfflineHoleNumber),0) from #OfflineHoles --Punch the holes out of the online times While @HoleNumber > 0 Begin insert into #Online select -1 Rownum, #Online.emp_num, #Online.day_date, #OfflineHoles.EndDateTime StartDateTime, #Online.EndDateTime EndDateTime from #Online join #OfflineHoles on #Online.Rownum = #OfflineHoles.Rownum where OfflineHoleNumber = @HoleNumber update #Online set #Online.EndDateTime = #OfflineHoles.StartDateTime from #Online join #OfflineHoles on #Online.Rownum = #OfflineHoles.Rownum where OfflineHoleNumber = @HoleNumber set @HoleNumber=@HoleNumber-1 end --Output total hours select emp_num, day_date, SUM(datediff(second,StartDateTime, EndDateTime)) / 3600.0 TotalHr, SUM(datediff(second,StartDateTime, EndDateTime)) / 60.0 TotalMin from #Online group by emp_num, day_date order by 1, 2 --see how it split up the online intervals select emp_num, day_date, StartDateTime, EndDateTime from #Online order by 1, 2, 3, 4
输出是:
emp_num day_date TotalHr TotalMin ----------- ----------------------- --------------------------------------- --------------------------------------- 547 2015-04-01 00:00:00.000 4.100000 246.000000 547 2015-04-02 00:00:00.000 0.816666 49.000000 548 2015-04-01 00:00:00.000 0.750000 45.000000 (3 row(s) affected) emp_num day_date StartDateTime EndDateTime ----------- ----------------------- ----------------------- ----------------------- 547 2015-04-01 00:00:00.000 2015-04-01 17:24:00.000 2015-04-01 21:30:00.000 547 2015-04-02 00:00:00.000 2015-04-02 13:11:00.000 2015-04-02 14:00:00.000 548 2015-04-01 00:00:00.000 2015-04-01 12:30:00.000 2015-04-01 13:15:00.000 (3 row(s) affected)
我留下了我的另一个答案,因为它更通用,以防其他人想要抓住它。 我看到你为这个问题增加了一笔赏金。 让我知道,如果我的答案有一些特定的东西不能满足你,我会尽力帮助你。 我使用这种方法处理数千个间隔,它只需几秒钟即可返回。
我不得不解决这个问题来消化一些调度数据。 这允许多个在线时间,但假设它们不重叠。
select convert(datetime,'1/1/2015 5:00 AM') StartDateTime, convert(datetime,'1/1/2015 5:00 PM') EndDateTime, convert(varchar(20),'Online') IntervalType into #CapacityIntervals insert into #CapacityIntervals select '1/1/2015 4:00 AM' StartDateTime, '1/1/2015 6:00 AM' EndDateTime, 'Offline' IntervalType insert into #CapacityIntervals select '1/1/2015 5:00 AM' StartDateTime, '1/1/2015 6:00 AM' EndDateTime, 'Offline' IntervalType insert into #CapacityIntervals select '1/1/2015 10:00 AM' StartDateTime, '1/1/2015 12:00 PM' EndDateTime, 'Offline' IntervalType insert into #CapacityIntervals select '1/1/2015 11:00 AM' StartDateTime, '1/1/2015 1:00 PM' EndDateTime, 'Offline' IntervalType insert into #CapacityIntervals select '1/1/2015 4:00 PM' StartDateTime, '1/1/2015 6:00 PM' EndDateTime, 'Offline' IntervalType insert into #CapacityIntervals select '1/1/2015 1:30 PM' StartDateTime, '1/1/2015 2:00 PM' EndDateTime, 'Offline' IntervalType --Populate your Offline table select ROW_NUMBER() over (Order by StartDateTime, EndDateTime) Rownum, StartDateTime, EndDateTime into #Offline from #CapacityIntervals where IntervalType in ('Offline','Cleanout') group by StartDateTime, EndDateTime --Populate your Online table select ROW_NUMBER() over (Order by StartDateTime, EndDateTime) Rownum, StartDateTime, EndDateTime into #Online from #CapacityIntervals where IntervalType not in ('Offline','Cleanout') --If you have overlapping online intervals... check for those here and consolidate. ------------------------------- --find overlaping offline times ------------------------------- declare @Finished as tinyint set @Finished = 0 while @Finished = 0 Begin update #Offline set #Offline.EndDateTime = OverlapEndDates.EndDateTime from #Offline join ( select #Offline.Rownum, MAX(Overlap.EndDateTime) EndDateTime from #Offline join #Offline Overlap on Overlap.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Offline.Rownum <= Overlap.Rownum group by #Offline.Rownum ) OverlapEndDates on #Offline.Rownum = OverlapEndDates.Rownum --Remove Online times completely inside of online times delete #Offline from #Offline join #Offline Overlap on #Offline.StartDateTime between Overlap.StartDateTime and Overlap.EndDateTime and #Offline.EndDateTime between Overlap.StartDateTime and Overlap.EndDateTime and #Offline.Rownum > Overlap.Rownum --LOOK IF THERE ARE ANY MORE CHAINS LEFT IF NOT EXISTS( select #Offline.Rownum, MAX(Overlap.EndDateTime) EndDateTime from #Offline join #Offline Overlap on Overlap.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Offline.Rownum < Overlap.Rownum group by #Offline.Rownum ) SET @Finished = 1 END ------------------------------- --Modify Online times with offline ranges ------------------------------- --delete any Online times completely inside offline range delete #Online from #Online join #Offline on #Online.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Online.EndDateTime between #Offline.StartDateTime and #Offline.EndDateTime --Find Online Times with offline range at the beginning update #Online set #Online.StartDateTime = #Offline.EndDateTime from #Online join #Offline on #Online.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime and #Online.EndDateTime >= #Offline.EndDateTime --Find Online Times with offline range at the end update #Online set #Online.EndDateTime = #Offline.StartDateTime from #Online join #Offline on #Online.StartDateTime <= #Offline.StartDateTime and #Online.EndDateTime between #Offline.StartDateTime and #Offline.EndDateTime --Find Online Times with offline range punched in the middle select #Online.Rownum, #Offline.Rownum OfflineRow, #Offline.StartDateTime, #Offline.EndDateTime, ROW_NUMBER() over (Partition by #Online.Rownum order by #Offline.Rownum Desc) OfflineHoleNumber into #OfflineHoles from #Online join #Offline on #Offline.StartDateTime between #Online.StartDateTime and #Online.EndDateTime and #Offline.EndDateTime between #Online.StartDateTime and #Online.EndDateTime declare @HoleNumber as integer select @HoleNumber = isnull(MAX(OfflineHoleNumber),0) from #OfflineHoles --Punch the holes out of the online times While @HoleNumber > 0 Begin insert into #Online select -1 Rownum, #OfflineHoles.EndDateTime StartDateTime, #Online.EndDateTime EndDateTime from #Online join #OfflineHoles on #Online.Rownum = #OfflineHoles.Rownum where OfflineHoleNumber = @HoleNumber update #Online set #Online.EndDateTime = #OfflineHoles.StartDateTime from #Online join #OfflineHoles on #Online.Rownum = #OfflineHoles.Rownum where OfflineHoleNumber = @HoleNumber set @HoleNumber=@HoleNumber-1 end --Output total hours select SUM(datediff(second,StartDateTime, EndDateTime)) / 3600.0 TotalHr from #Online --see how it split up the online intervals select * from #Online order by StartDateTime, EndDateTime
我的解决方案与弗拉基米尔·巴拉诺夫非常相似。
链接到.NetFiddle
大概的概念
我的算法基于区间树的修改。 它假设最小的时间单位是1分钟(易于修改)。
每个树节点处于3个状态中的1个:未访问,访问和使用。 该算法基于递归搜索function,可通过以下步骤进行描述:
- 如果使用节点或搜索间隔为空,则返回空间隔。
- 如果未访问节点且节点间隔等于搜索间隔,则将当前节点标记为已使用并返回节点间隔。
- 将节点标记为已访问 ,分割搜索间隔并返回搜索左右子节点的总和。
解决方案步骤
- 计算最大间隔。
- 添加到树“次要间隔”。
- 添加到树“主要间隔”。
-
计算间隔总和。
请注意我假设间隔是[开始; 结束],即两个区间都是包容性的,什么是容易改变的。
要求
假设
n – “次要间隔”的数量
m – 基本单位的最大时间
构造需要O(2n)存储空间并在O(n log n + m)时间内工作。
这是我的代码
public class Interval { public int Start { get; set; } public int End { get; set; } }; enum Node { Unvisited = 0, Visited = 1, Used = 2 }; Node[] tree; public void Calculate() { var secondryIntervalsAsDates = new List> { new Tuple( new DateTime(2015, 03, 15, 4, 0, 0), new DateTime(2015, 03, 15, 5, 0, 0))}; var mainInvtervalAsDate = new Tuple(new DateTime(2015, 03, 15, 3, 0, 0), new DateTime(2015, 03, 15, 7, 0, 0)); // calculate biggest interval var startDate = secondryIntervalsAsDates.Union( new List>{mainInvtervalAsDate}).Min(s => s.Item1).AddMinutes(-1); var endDate = secondryIntervalsAsDates.Union(new List> { mainInvtervalAsDate }).Max(s => s.Item2); var mainInvterval = new Interval { Start = (int)(mainInvtervalAsDate.Item1 - startDate).TotalMinutes, End = (int)(mainInvtervalAsDate.Item2 - startDate).TotalMinutes }; var wholeInterval = new Interval { Start = 1, End = (int)(endDate - startDate).TotalMinutes}; //convert intervals to minutes var secondaryIntervals = secondryIntervalsAsDates.Select(s => new Interval { Start = (int)(s.Item1 - startDate).TotalMinutes, End = (int)(s.Item2 - startDate).TotalMinutes}).ToList(); tree = new Node[wholeInterval.End * 2 + 1]; //insert secondary intervals secondaryIntervals.ForEach(s => Search(wholeInterval, s, 1)); //insert main interval var result = Search(wholeInterval, mainInvterval, 1); //calculate result var minutes = result.Sum(r => r.End - r.Start) + result.Count(); } public IEnumerable Search(Interval current, Interval searching, int index) { if (tree[index] == Node.Used || searching.End < searching.Start) { return new List (); } if (tree[index] == Node.Unvisited && current.Start == searching.Start && current.End == searching.End) { tree[index] = Node.Used; return new List { current }; } tree[index] = Node.Visited; return Search(new Interval { Start = current.Start, End = current.Start + (current.End - current.Start) / 2 }, new Interval { Start = searching.Start, End = Math.Min(searching.End, current.Start + (current.End - current.Start) / 2) }, index * 2).Union( Search(new Interval { Start = current.Start + (current.End - current.Start) / 2 + 1 , End = current.End}, new Interval { Start = Math.Max(searching.Start, current.Start + (current.End - current.Start) / 2 + 1), End = searching.End }, index * 2 + 1)); }
这是带有完整查询的SQLFiddle 。
我将展示如何构建一个返回每个emp_num, day_date
的分钟数的查询。 如果事实certificate没有为特定的emp_num, day_date
留下任何分钟,那么结果将没有一行为0
,根本就没有这样的行。
大概的概念
我会用一张数字表 。 我们只需要24*60=1440
数字,但在数据库中为其他报告提供此类表是个好主意。 我个人拥有10万行。 这是一篇非常好的文章,比较了生成这样的表的不同方法。
对于每个间隔,我将使用数字表生成一组行 – 间隔中每分钟一行。 我假设间隔是[start; end)
[start; end)
,即开始分钟是包容性的,结束分钟是独家的。 例如,从07:00
到08:00
间隔是60
分钟,而不是61
。
生成一个数字表
DECLARE @Numbers TABLE (N int); INSERT INTO @Numbers(N) SELECT TOP(24*60) ROW_NUMBER() OVER(ORDER BY S.object_id) - 1 AS N FROM sys.all_objects AS S ORDER BY N ;
对于此任务,最好使用从0开始的数字。通常,您可以将其作为具有N
主键的永久表。
样本数据
DECLARE @Missions TABLE (emp_num int, day_date datetime, mission_in datetime, mission_out datetime); DECLARE @Periods TABLE (emp_num int, day_date datetime, work_st datetime, work_end datetime, check_in datetime, check_out datetime, day_state char(1)); INSERT INTO @Missions (emp_num, day_date, mission_in, mission_out) VALUES (547, '2015-04-01', '2015-04-01 15:00:00', '2015-04-01 21:30:00'), (547, '2015-04-02', '2015-04-02 08:00:00', '2015-04-02 14:00:00'); INSERT INTO @Periods (emp_num, day_date, work_st, work_end, check_in, check_out, day_state) VALUES (547, '2015-04-01', '2015-04-01 08:00:00', '2015-04-01 16:00:00', '2015-04-01 07:45:00', '2015-04-01 12:10:00', 'W'), (547, '2015-04-01', '2015-04-01 08:00:00', '2015-04-01 16:00:00', '2015-04-01 12:45:00', '2015-04-01 17:24:00', 'W'), (547, '2015-04-02', '2015-04-02 00:00:00', '2015-04-02 00:00:00', '2015-04-02 07:11:00', '2015-04-02 13:11:00', 'E');
我的解决方案不会使用day_state
列。 我希望你的work_st
和work_end
都有00:00:00
。 解决方案期望同一行中的日期组件相同,而day_date
没有时间组件。
如果我为这个任务设计了模式,我将有三个表而不是两个: Missions
, WorkPeriods
和CheckPeriods
。 我会将你的表Periods
分成两部分,以避免在多行中重复work_st
和work_end
。 但是这个解决方案将处理您当前的架构,它将基本上生成第三个表。 在实践中,这意味着可以提高性能。
任务分钟
WITH CTE_MissionMinutes AS ( SELECT emp_num, day_date, NN FROM @Missions AS M CROSS JOIN @Numbers AS N WHERE NN >= DATEDIFF(minute, M.day_date, M.mission_in) AND NN < DATEDIFF(minute, M.day_date, M.mission_out) )
来自@Missions
每个原始行变成一组行,每个分钟对应一个行(mission_in, mission_out)
。
工作期间
,CTE_WorkPeriods AS ( SELECT P.emp_num, P.day_date, P.work_st, P.work_end FROM @Periods AS P GROUP BY P.emp_num, P.day_date, P.work_st, P.work_end )
生成第三个辅助表 - 每个emp_num, day_date, work_st, work_end
一个行emp_num, day_date, work_st, work_end
- (work_st, work_end)
所有间隔。
工作和检查分钟
,CTE_WorkMinutes AS ( SELECT emp_num, day_date, NN FROM CTE_WorkPeriods CROSS JOIN @Numbers AS N WHERE NN >= DATEDIFF(minute, CTE_WorkPeriods.day_date, CTE_WorkPeriods.work_st) AND NN < DATEDIFF(minute, CTE_WorkPeriods.day_date, CTE_WorkPeriods.work_end) ) ,CTE_CheckMinutes AS ( SELECT emp_num, day_date, NN FROM @Periods AS P CROSS JOIN @Numbers AS N WHERE NN >= DATEDIFF(minute, P.day_date, P.check_in) AND NN < DATEDIFF(minute, P.day_date, P.check_out) )
与Missions
完全相同。
联盟“次要间隔”
,CTE_UnionPeriodMinutes AS ( SELECT emp_num, day_date, N FROM CTE_WorkMinutes UNION ALL -- can be not ALL here, but ALL is usually faster SELECT emp_num, day_date, N FROM CTE_CheckMinutes )
从主要减去次要间隔
,CTE_FinalMinutes AS ( SELECT emp_num, day_date, N FROM CTE_MissionMinutes EXCEPT SELECT emp_num, day_date, N FROM CTE_UnionPeriodMinutes )
总结分钟数
SELECT emp_num ,day_date ,COUNT(*) AS FinalMinutes FROM CTE_FinalMinutes GROUP BY emp_num, day_date ORDER BY emp_num, day_date;
要进行最终查询,只需将所有CTE放在一起。
结果集
emp_num day_date FinalMinutes 547 2015-04-01 00:00:00.000 246 547 2015-04-02 00:00:00.000 49 There are 246 minutes between 17:24 and 21:30. There are 49 minutes between 13:11 and 14:00.
这是带有完整查询的SQLFiddle 。
显示导致此分钟SUM
的实际间隔相当容易,但您说您只需要SUM
。
我发现可能是最简单的解决方案。
.netFiddle
- 按开始日期排序“次要间隔”。
- 寻找“次要区间”中的空白(简单迭代)
-
将间隙与“主间隔”进行比较。
//declare intervals var secondryIntervals = new List
> { new Tuple ( new DateTime(2015, 03, 15, 4, 0, 0), new DateTime(2015, 03, 15, 5, 0, 0)), new Tuple ( new DateTime(2015, 03, 15, 4, 10, 0), new DateTime(2015, 03, 15, 4, 40, 0)), new Tuple ( new DateTime(2015, 03, 15, 4, 40, 0), new DateTime(2015, 03, 15, 5, 20, 0))}; var mainInterval = new Tuple (new DateTime(2015, 03, 15, 3, 0, 0), new DateTime(2015, 03, 15, 7, 0, 0)); // add two empty intervals before and after main interval secondryIntervals.Add(new Tuple (mainInterval.Item1.AddMinutes(-1), mainInterval.Item1.AddMinutes(-1))); secondryIntervals.Add(new Tuple (mainInterval.Item2.AddMinutes(1), mainInterval.Item2.AddMinutes(1))); secondryIntervals = secondryIntervals.OrderBy(s => s.Item1).ToList(); // endDate will rember 'biggest' end date var endDate = secondryIntervals.First().Item1; var result = secondryIntervals.Select(s => { var temp = endDate; endDate = endDate < s.Item2 ? s.Item2 : endDate; if (s.Item1 > temp) { return new Tuple (temp < mainInterval.Item1 ? mainInterval.Item1 : temp, mainInterval.Item2 < s.Item1 ? mainInterval.Item2 : s.Item1); } return null; }) // remove empty records .Where(s => s != null && s.Item2 > s.Item1).ToList(); var minutes = result.Sum(s => (s.Item2 - s.Item1).TotalMinutes);
该算法需要O(n log n)时间(用于排序)而无需额外的存储和假设。