将重叠的日期范围转换为日期范围的行

时间:2018-11-12 16:55:13

标签: tsql datetime

我在一个表中有一个日期范围列表,该范围可以是开放式的(enddate = null):

Index   startdate   enddate
1       2018-07-13  NULL
2       2018-11-14  2018-11-16
3       2018-11-15  2018-11-15

查询测试数据:

DECLARE @ScheduleTable Table([Index] int not null, StartDate DateTime not null, EndDate DateTime null)
insert into @ScheduleTable ([Index], StartDate, EndDate)
values
(1,'2018-07-13',null)
, (2,'2018-11-14','2018-11-16')
, (3,'2018-11-15','2018-11-15')
select*from @ScheduleTable

如何编写一个查询,以“填补空缺”并返回以下结果:

Index   startdate   enddate
1       2018-07-13  2018-11-13
2       2018-11-14  2018-11-14
3       2018-11-15  2018-11-15
2       2018-11-16  2018-11-16
1       2018-11-17  NULL

查询显示预期结果:

select 
1 as [Index], '2018-07-13' as StartDate, '2018-11-13' as EndDate
UNION ALL
select 
2 as [Index], '2018-11-14', '2018-11-14'
UNION ALL
select 
3 as [Index], '2018-11-15', '2018-11-15'
UNION ALL
select 
2 as [Index], '2018-11-16', '2018-11-16'
UNION ALL
select 
1 as [Index], '2018-11-17', null




我希望答案不涉及参数/临时表等。如果有帮助,我可以使用日期维度表。

在上面的示例中,Index = 1的条目是开放式的,并从7.13开始。在11.14上被Index = 2中断。然后,Index = 2在11.15上被Index = 3打断。索引= 2然后从11.16重新开始。紧随其后的Index = 1是从11.17开始重新启动

索引决定了优先顺序,因此Index = 2将覆盖11.14-11.16上的Index = 1,Index = 3将覆盖11.15上的Index = 2 。


这是我当前使用lead()的查询:

DECLARE @MinDate DateTime = '2015-01-01'
DECLARE @MaxDate DateTime = '2020-01-01'

select
row_number() over(partition by dealid order by ss.StartDate, ss.id) as [Index]
, ss.startdate
, ss.enddate
, case when ss.enddate is null then
    dateadd(d,-1,lead(ss.startdate,1,@MaxDate) over(partition by dealid order by ss.startdate, ss.id)) 
    else ss.enddate end
    as EndDate
from
[dbo].[Schedule]ss
where ss.enabled = 1

1 个答案:

答案 0 :(得分:0)

我可以使用以下方法解决问题:

步骤:

  • 从DateDimension表中获取日期表
  • 将时间表加入DateDimension表
  • row_number计划,以确定在给定日期中哪个计划优先
  • 排名结果,按日期排序
  • dense_rank结果,按计划ID划分,按日期排序
  • 从等级结果中减去density_rank结果,为每组连续日期创建唯一的ID
  • 获取每个日期范围的最小和最大日期

查询以填充测试数据:

DECLARE @ScheduleTable Table([Index] int not null, StartDate DateTime not null, EndDate DateTime null)
insert into @ScheduleTable ([Index], StartDate, EndDate)
values
(1,'2018-07-13',null)
, (2,'2018-11-14','2018-11-16')
, (3,'2018-11-15','2018-11-15')

解决方案:

DECLARE @MinDate Date = dateadd(year,-2,getdate())
DECLARE @MaxDate DateTime = dateadd(year,2,getdate())

select 
min(dt) as StartDate
, max(dt) as EndDate
, dense_rank() over(Order by [Index]) [Index]
from
(
select
--Create "groups" using a raw Rank minus dense_rank, partitioned by [Index]
rank() over(order by dt) - dense_rank() over(partition by [Index] order by dt) qlt,
[Index], dt
from
(
select
--Apply row_number to identify which schedule takes precedence on a given day
--Index=2 takes precedence over Index=1
row_number() over(partition by inr.[date] order by ss.[Index] desc) rm,
[date] dt
, ss.*
from
(
    --Obtain Table of Dates from DateDimension table
    select
    [date]
    from
    [dbo].[DateDimension]dd
    where dd.[date] >= @MinDate
    and dd.[date] <= @MaxDate
)inr
    --join schedules to DateDimension table
    left join 
    (
        select *
        from
        @ScheduleTable
    )ss
        on ss.StartDate <= inr.[date]
        and (ss.enddate >= inr.[date]
            or ss.enddate is null)

)inr2
--Exclude any Schedule that is not row_number=1
where inr2.rm = 1
and inr2.[Index] is not null
)inr3
--Group on Index first then Rank minus dense_rank, partitioned by [Index]
group by [Index], qlt
order by StartDate
, [Index]

结果:

StartDate   EndDate     Index
2018-07-13  2018-11-13  1
2018-11-14  2018-11-14  2
2018-11-15  2018-11-15  3
2018-11-16  2018-11-16  2
2018-11-17  2020-11-12  1