【问题标题】:SQL Server separate overlapping datesSQL Server 分隔重叠日期
【发布时间】:2014-07-17 06:00:38
【问题描述】:

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

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

【问题讨论】:

  • 我不明白你的问题。你能说得更具体些吗?

标签: sql sql-server database tsql


【解决方案1】:

我暂时忽略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 列,因为在列名中使用保留字会变得很痛苦)

【讨论】:

  • 如果原始数据中存在空白,则存在问题。要解决此问题,您需要将此结果与原始数据重叠
【解决方案2】:

考虑将范围视为几何线,然后使用 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 的空间工具在一个方面受到限制。当您需要查询“几何”值内的单个形状或点时,您必须传递索引号才能获取它。在微软推出可以输出所有形状的 TVF 之前,我们使用数字表来完成工作。

换成你喜欢的方法来创建一个数字表:

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

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

在“地理聚合”中:

  • 我将日期转换为浮点值。我在停止值上加一 使几何在实数空间中工作,而不是 单位之间没有值的“整数空间”。
  • 我将浮点值转换为几何点
  • 我用这些点画线,我也持有这些点 一起形成一个称为“分割器”的几何空间
  • 我聚合行并聚合拆分器。对于线路 如果它们重叠,这会将它们合并到一个范围内。对于分离器, 它只是将它们进一步浓缩为一个集合。

在外部查询中:

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

这里是代码。它按您的预期输出,除了“A”的第二行中的 type-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;

【讨论】:

  • 虽然我已经忘记了这件事,但我为你的提问和解释鼓掌。谢谢。
猜你喜欢
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
  • 2020-06-22
  • 2014-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多