这是一个不使用游标的解决方案。我不知道它在大型数据集上的速度有多快,所以希望您可以针对基于光标的方法对其进行测试,并告诉我它是如何保持的。代码后面有更详细的解释。
-- Get a list of all dates on which coverage starts or stops.
with [EventsCTE] as
(
select [id], [startdate] as [date], 1 as [change] from dateranges
union all
select [id], [enddate] as [date], -1 as [change] from dateranges
),
-- Give each event a sequence number (by date) within its id.
[SequencedEventsCTE] as
(
select row_number() over (partition by [id] order by [date]) as [seq], *
from [EventsCTE]
),
-- Use the sequence number to construct a running total of the number of active
-- date ranges at each point in time.
[RunningTotalsCTE] as
(
-- Base case: Get the first event for each id.
select *, [change] as [rangesActive]
from [SequencedEventsCTE] where [seq] = 1
union all
-- Recursive case: build a running total for subsequent events.
select [this].*, [this].[change] + [prev].[rangesActive] as [rangesActive]
from [SequencedEventsCTE] [this]
inner join [RunningTotalsCTE] [prev] on
[this].[Id] = [prev].[Id] and
[this].[seq] = [prev].[seq] + 1
),
-- Join each event to its successor and look for dates on which no range was
-- active. This gives us a list of gaps and their sizes.
[GapsCTE] as
(
select [gapStart].[Id],
datediff(day, [gapStart].[date], [gapEnd].[date]) as [GapSize]
from [RunningTotalsCTE] [gapStart]
inner join [RunningTotalsCTE] [gapEnd] on
[gapStart].[Id] = [gapEnd].[Id] and
[gapStart].[seq] = [gapEnd].[seq] - 1 and
[gapStart].[rangesActive] = 0
)
-- Get the ids having gaps of 20 days or more.
select distinct [id] from [GapsCTE] where [GapSize] >= 20;
首先,在EventsCTE 中,我将原始表中的每一行拆分为两个“事件”,一个表示日期范围已经开始(这些记录有change = 1),另一个表示日期范围已结束 (change = -1)。从这个开始似乎是必要的,因为你有重叠的范围;我无法通过仅将原始表中的一条记录与其后面的记录进行比较来识别差距。
SequencedEventsCTE 采用此扩展数据集并添加一个新列seq,它给出了每个id 中特定事件的相对顺序。这让我可以在下一步中轻松地将每个事件与紧接在它之前的事件相匹配。
RunningTotalsCTE 具有使整个事情起作用的技巧:对于每个事件,它计算每个 id 内的 change 值的运行总数。因此,该运行总数rangesActive 应给出截至每个事件日期处于活动状态的日期范围的数量。这使我可以考虑重叠的日期范围。例如,如果您从RunningTotalsCTE 中选择所有记录,其中id = 3,您会得到以下结果:
seq id date change rangesActive
1 3 2012-01-01 00:00:00.000 1 1
2 3 2013-01-01 00:00:00.000 1 2
3 3 2013-02-28 00:00:00.000 -1 1
4 3 2013-07-01 00:00:00.000 1 2
5 3 2014-01-01 00:00:00.000 -1 1
6 3 2014-06-01 00:00:00.000 -1 0
最后,GapsCTE 通过查找rangesActive = 0 所在的记录来识别所有间隙,不包括每个id 中的最后一个事件。差距的大小是此类记录的事件日期与其后记录的事件日期之间的差异。最后一步是简单地从这个最终 CTE 中选择唯一的 ids,其中差距大小为 20 天或更长时间。
我认为这将满足您的需求,但正如我所说,我不确定它在处理非常大的数据集时的表现如何。如果您对它的工作原理有任何具体问题,请发表评论。