如何在SQL Server中递归地查找重叠时段

时间:2014-04-16 00:24:41

标签: sql sql-server recursion common-table-expression

我有这个示例数据集

ID  StartDate   EndDate
------------------------------
1   2014-01-05  2014-01-10
2   2014-01-06  2014-01-11
3   2014-01-07  2014-01-12
4   2014-01-08  2014-01-13
5   2014-01-09  2014-01-14
6   2014-01-26  2014-01-31
7   2014-01-27  2014-02-01
8   2014-01-28  2014-02-02
9   2014-01-29  2014-02-03
10  2014-01-30  2014-02-04

我想选择与提供的时间段重叠的任何行,以及与之重叠的任何行等等。

因此,如果我想选择2014-01-06至2014-01-07期间的任何行数

以下是直接重叠(立即重叠)

1   2014-01-05  2014-01-10
2   2014-01-06  2014-01-11

但我还需要与1和2重叠的行(子重叠)

3   2014-01-07  2014-01-12
4   2014-01-08  2014-01-13
5   2014-01-09  2014-01-14

如果3到5有重叠,也要返回它们。但在这种情况下,没有。

这是我到目前为止的尝试,但它有两个问题,我发现我不知道如何修复。

;WITH cte
AS 
(
    SELECT  t.ID, 
            t.StartTime, 
            t.EndTime
    FROM    
            dbo.Tasks AS t
    UNION ALL
    SELECT  t.ID, 
            t.StartTime, 
            t.EndTime 
    FROM    
            dbo.Tasks AS t INNER JOIN
            cte AS c ON t.StartTime < c.EndTime
                    AND t.EndTime > c.StartTime

)
SELECT * FROM cte AS a WHERE   a.StartTime < @NewEnd
                    AND a.EndTime > @NewStart

当获得子重叠的重叠时段时,重新包含立即重叠并导致无限递归。其次,

SELECT * FROM cte AS a WHERE   a.StartTime < @NewEnd
                        AND a.EndTime > @NewStart

where子句将过滤掉任何递归发现的重叠。

2 个答案:

答案 0 :(得分:1)

我首先要弄清楚数据集中岛屿的位置,然后才能确定哪些岛屿与您的查询范围重叠:

declare @t table (ID int,StartDate date,EndDate date)
insert into @t(ID,StartDate,EndDate) values
(1   ,'20140105','20140110'),
(2   ,'20140106','20140111'),
(3   ,'20140107','20140112'),
(4   ,'20140108','20140113'),
(5   ,'20140109','20140114'),
(6   ,'20140126','20140131'),
(7   ,'20140127','20140201'),
(8   ,'20140128','20140202'),
(9   ,'20140129','20140203'),
(10  ,'20140130','20140204')

declare @Start date
declare @End date
select @Start='20140106',@End='20140107'

;With PotIslands as (
    --Find ranges which aren't overlapped at their start
    select StartDate,EndDate from @t t where
        not exists (select * from @t t2 where
                      t2.StartDate < t.StartDate and
                      t2.EndDate >= t.StartDate)
    union all
    --Extend the ranges by any other ranges which overlap on the end
    select pi.StartDate,t.EndDate
    from PotIslands pi
            inner join
        @t t
            on
                pi.EndDate >= t.StartDate and pi.EndDate < t.EndDate
), Islands as (
    select StartDate,MAX(EndDate) as EndDate from PotIslands group by StartDate
)
select * from Islands i where @Start <= i.EndDate and @End >= i.StartDate

结果:

StartDate  EndDate
---------- ----------
2014-01-05 2014-01-14

如果您需要单独的行,现在可以将选定的岛加入@t表,以进行简单的范围查询。

这是有效的,例如,如果某个岛内的任何行都包含在一个范围内,岛上的所有剩余行也将始终。所以我们先找到这些岛屿。

答案 1 :(得分:0)

这应该这样做,如果需要你可以将它放入一个函数中并使用交叉应用来连接到另一个表。我没有测试它,但它应该与最小(如果有)错误一起工作。

declare @rt table
(
    ID int not null,
    StartTime date not null,
    EndTime date not null
)

insert into @rt (ID, StartTime, EndTime)
select t.*
from Tasks t
where (@StartTime <= t.StartTime and @EndTime  > t.StartTime)
    or (@StartTime < t.EndTime and @EndTime  >= t.EndTime)

declare @found int = @@rowcount

while @found > 0
begin
    insert into @rt (ID, StartTime, EndTime)
    select t.*
    from Tasks t
    left join @rt rt
        on (rt.StartTime <= t.StartTime and rt.EndTime  > t.StartTime)
        or (rt.StartTime < t.EndTime and rt.EndTime  >= t.EndTime)
    where t.ID not in (select ID from @rt)

    set @found = @@rowcount
end

select * from @rt