SQL Server分隔重叠日期

时间:2014-07-17 06:00:39

标签: sql sql-server database tsql

我有一张包含以下数据的表格:

enter image description here

如果某个类型的日期重叠,我想为重叠期间返回一个单独的行,最后会得到以下结果:

enter image description here

2 个答案:

答案 0 :(得分:6)

我现在忽略PKey,因为我不确定它是否与问题实际相关。

这解决了这个问题:

declare @t table (PKey int,Start date,[End] date,Type char(1))
insert into @t(PKey,Start,[End],Type) values
(1,'20100101','20100114','S'),
(2,'20100110','20100131','S'),
(3,'20100105','20100130','A'),
(4,'20100124','20100206','A'),
(5,'20100120','20100127','T'),
(6,'20100128','20100130','T')

;With EndDates as (
    select [End],Type from @t
    union all
    select DATEADD(day,-1,Start),Type from @t
), Periods as (
    select Type,MIN(Start) as Start,
                (select MIN([End]) from EndDates e
                 where e.Type = t.Type and
                 e.[End] >= MIN(Start)) as [End]
    from
        @t t
    group by Type
    union all
    select p.Type,DATEADD(day,1,p.[End]),e.[End]
    from
        Periods p
            inner join
        EndDates e
            on
                p.Type = e.Type and
                p.[End] < e.[End]
    where
        not exists (select * from EndDates e2 where
                e2.Type = p.Type and
                e2.[End] > p.[End] and
                e2.[End] < e.[End])
)
select * from Periods
order by Type,Start

首先,我们创建一个名为EndDates的CTE,其中包含可能是一段时间结束的所有日期 - 这些日期是我们数据中已有的结束日期,或者它们是当天的结束日期在我们的数据中的一个开始日期之前。

然后我们建立期间 - 首先我们找到任何特定类型的第一个期间 - 我们采用最早的开始日期,以及开始日期之后的最早可能的结束日期。

然后,递归地,我们通过在现有结束后的第二天开始新的期间来建立额外的期间,并找到该日期之后的最早结束日期。

然后,基本上,我们已经完成了。结果:

Type Start      End
---- ---------- ----------
A    2010-01-05 2010-01-23
A    2010-01-24 2010-01-30
A    2010-01-31 2010-02-06
S    2010-01-01 2010-01-09
S    2010-01-10 2010-01-14
S    2010-01-15 2010-01-31
T    2010-01-20 2010-01-27
T    2010-01-28 2010-01-30

哪个与您问题中的内容完全匹配,但我认为2月30日结束的A行是拼写错误。

(我建议您重命名End列,因为对列名使用保留字会变成真正的痛苦)

答案 1 :(得分:0)

考虑将范围视为几何线,然后使用SQL Server的几何工具。

首先,我需要将您的数据放入一个可以使用的表中:

declare @spans table (
    type char(1),
    start datetime,
    stop datetime
);

insert @spans values 
    ('S', '2010-01-01', '2010-01-14'),
    ('S', '2010-01-10', '2010-01-31'),
    ('A', '2010-01-05', '2010-01-30'),
    ('A', '2010-01-24', '2010-02-06'),
    ('T', '2010-01-20', '2010-01-27'),
    ('T', '2010-01-28', '2010-01-30');

不幸的是,SQL Server的空间工具在一方面受到限制。当您需要查询“几何”值中的单个形状或点时,必须传递索引号才能获取它。在Microsoft推出输出所有形状的TVF之前,我们一直使用数字表来完成工作。

用您喜欢的方法将其交换出来以创建数字表:

declare     @numbers table (i int);
insert      @numbers    
select      i = row_number() over (order by (select null))
from        @spans a, @spans b;

现在您已准备好进行主查询。

在“ geoAggregates”中:

  • 我将日期转换为浮点值。我在停止值上加一 使几何在实数空间中工作,而不是 单元之间没有值的“整数空间”。
  • 我将浮点值转换为几何点
  • 我在这些要点上划线,我也坚持要点 一起放入称为“分离器”的单个几何空间
  • 我汇总行,然后汇总拆分器。对于线 如果它们重叠,则将它们合并到一个范围内。对于分离器, 只是将它们进一步浓缩为一个集合。

在外部查询中:

  • 我用分割线分割线。拆分器需要一个缓冲区 赋予它们非零长度,以便实际执行拆分。
  • 我将分割行的集合提取为单独的行
  • 我将这些行括起来以确保它们仅由其代表 端点。
  • 我解析端点以获取浮点值,撤消添加的值 停止,然后四舍五入以消除缓冲效果(和浮动 存储效果)。
  • 我将这些浮点值转换回日期表示形式。

这里是代码。它会按预期输出,除了在“ A”的第二行中键入o之外。

with

    geoAggregates as (

        select      type,
                    lines = geometry::UnionAggregate(line),
                    splitters = geometry::UnionAggregate(splitters)
        from        @spans
        cross apply (select 
                        startF = convert(float, start),
                        stopF = convert(float, stop) + 1
                    ) prepare
        cross apply (select 
                        startP = geometry::Point(startF, 0, 0),
                        stopP = geometry::Point(stopF, 0, 0)
                    ) pointify
        cross apply (select 
                        line = startP.STUnion(stopP).STEnvelope(),
                        splitters = startP.STUnion(stopP)
                    ) lineify
        group by    type 

    )

    select      type, 
                start,
                stop 
    from        geoAggregates ga
    cross apply (select 
                    splitted = ga.lines.STDifference(splitters.STBuffer(0.001))
                ) sp
    join        @numbers n on n.i between 1 and sp.splitted.STNumGeometries()
    cross apply (select 
                    line = sp.splitted.STGeometryN(i).STEnvelope()
                ) l
    cross apply (select 
                    start = convert(datetime, round(l.line.STPointN(1).STX,0)),
                    stop = convert(datetime, round(l.line.STPointN(3).STX - 1,0)) 
                ) dateify
    order by    type, start;