如何计算时间间隔?

我有一个问题,我解决了,但我写了一个很长的程序,我不能确定它涵盖了所有可能的情况。

问题:

如果我有一个主间隔时间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,可通过以下步骤进行描述:

  1. 如果使用节点或搜索间隔为空,则返回空间隔。
  2. 如果未访问节点且节点间隔等于搜索间隔,则将当前节点标记为已使用并返回节点间隔。
  3. 将节点标记为已访问 ,分割搜索间隔并返回搜索左右子节点的总和。

解决方案步骤

  1. 计算最大间隔。
  2. 添加到树“次要间隔”。
  3. 添加到树“主要间隔”。
  4. 计算间隔总和。

    请注意我假设间隔是[开始; 结束],即两个区间都是包容性的,什么是容易改变的。

要求

假设

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:0008: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_stwork_end都有00:00:00 。 解决方案期望同一行中的日期组件相同,而day_date没有时间组件。

如果我为这个任务设计了模式,我将有三个表而不是两个: MissionsWorkPeriodsCheckPeriods 。 我会将你的表Periods分成两部分,以避免在多行中重复work_stwork_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

  1. 按开始日期排序“次要间隔”。
  2. 寻找“次要区间”中的空白(简单迭代)
  3. 将间隙与“主间隔”进行比较。

      //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)时间(用于排序)而无需额外的存储和假设。