2017-02-23 86 views
3

我有两个表:一个包含日期范围(即ValidFromDateValidToDate列),另一个包含事件(即EventDate列)。使用TSQL的日期划分范围

我正在寻找一个脚本,将每个日期范围分成多个记录,如果它们之间发生事件。

例如,考虑以下内容。

范围:

ValidFrom | ValidTo | RangeId 
2010-01-01 | 2010-05-01 | 1 
2010-05-01 | 2010-05-20 | 2 
2010-05-20 | 2017-02-23 | 3 
2017-02-23 | 2020-12-31 | 4 

活动:

EventDate | EventId 
2010-03-15 | 101 
2011-04-15 | 102 
2015-05-15 | 103 

在这种情况下,一个事件(101)的范围1跨度期间发生的,所以记录应分成以下。这可以通过更新原始记录并插入另一个记录来完成,或者通过删除原始记录并插入两个新记录来完成。

ValidFrom | ValidTo | RangeId 
2010-01-01 | 2010-03-15 | 1 
2010-03-15 | 2010-05-01 | 5 

我起草了所有的解决方案一直有同样的问题:如果在1米日期范围发生的两个事件,该脚本将不能正确处理这些。我可以通过写一个for循环来解决这个问题,该循环只处理每个事件之前的事件已经完成处理,但这会导致性能很差。

任何人都可以给我一些指导如何做到这一点?也许这是一个普遍接受的解决方案?

+0

应该结果保留原始RangeIds或ID范围只应是唯一的。例如在你的情况下,你的2个事件是否有RangeIds 1和2,其他所有的都转换为3,4,5? – vitalygolub

+0

有多少事件可能落在现有范围内有任何硬限制?是最多两个,还是可以有一个不确定的数字? – AakashM

+0

RangeIds无关紧要,事件数量没有硬性限制。 –

回答

1

假设:Range中的初始数据是连续的; EventDate均落入由Range定义的日期范围内。

的关键,下面的方法是要认识到这三个种日期输入数据的 - ValidFromValidToEventDate - 有相同结果所需的输出。所需的输出是范围列表,但这些范围的边界日期是平等的,与源范围数据和源事件数据没有区别。

因此,假设该表的结构和样本数据:

DECLARE @Range TABLE (
    ValidFrom datetime, 
    ValidTo datetime, 
    RangeId int 
); 

DECLARE @Event TABLE (
    EventDate datetime, 
    EventId int 
); 

INSERT @Range VALUES 
('20100101', '20100501', 1), 
('20100501', '20100520', 2), 
('20100520', '20170223', 3), 
('20170223', '202', 4) 
; 

INSERT @Event VALUES 
('20100315', 101), 
('20110415', 102), 
('20150515', 103) 
; 

我们结合来源日期:

WITH RelevantDate (D) AS (
SELECT ValidFrom FROM @Range 
UNION 
SELECT ValidTo FROM @Range 
UNION 
SELECT EventDate FROM @Event 
) 

数他们:

, SequencedDate (D, Sequence) AS (
SELECT D, ROW_NUMBER() OVER (ORDER BY D) 
FROM RelevantDate 
) 

,并产生输出:

SELECT 
    D1.D AS ValidFrom, 
    D2.D AS ValidTo, 
    D1.Sequence AS RangeId 
FROM 
    SequencedDate D1 
    INNER JOIN SequencedDate D2 ON D1.Sequence + 1 = D2.Sequence 
; 

这产生的,其目的是Range个列表替换现有Range数据:

ValidFrom    ValidTo     RangeId 
----------------------- ----------------------- -------------------- 
2010-01-01 00:00:00.000 2010-03-15 00:00:00.000 1 
2010-03-15 00:00:00.000 2010-05-01 00:00:00.000 2 
2010-05-01 00:00:00.000 2010-05-20 00:00:00.000 3 
2010-05-20 00:00:00.000 2011-04-15 00:00:00.000 4 
2011-04-15 00:00:00.000 2015-05-15 00:00:00.000 5 
2015-05-15 00:00:00.000 2017-02-23 00:00:00.000 6 
2017-02-23 00:00:00.000 2020-12-31 00:00:00.000 7 
1

尝试下面的脚本。这里的RangeId将保持现有记录相同。

WITH Result 
as (
    select ValidFrom,ValidTo,RangeId 
    from [Ranges] 
    union 
    select null,EventDate,ROW_NUMBER() OVER(ORDER BY EventDate) + 
      (SELECT MAX(RangeId) FROM Ranges) 
    from [Events] 
) --CTE to get all ValidTo dates as list 
select isnull(lag(ValidTo) over (order by ValidTo), 
      (select MIN(ValidFrom) from Ranges)) as ValidFrom, 
     ValidTo,RangeId 
from Result 
order by ValidTo