2017-02-23 38 views
3

У меня есть две таблицы: одна содержит диапазоны дат (например, a и ValidToDate) и один содержит события (то есть столбец 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 или идентификаторами диапазон просто должен быть уникальным. Например, в вашем случае ваши 2 события имеют RangeIds 1 и 2, а все остальные - 3,4,5? – vitalygolub

+0

Есть ли какие-либо жесткие ограничения на то, сколько событий может попасть в существующий диапазон? Это не более двух, или может быть неопределенное число? – AakashM

+0

RangeIds не имеет значения, и нет жесткого предела количества событий. –

ответ

1

допущениях: исходные данные в Range смежный; EventDate s все относятся к диапазону дат, определяемому Range.

Ключ к ниже подходу состоит в том, чтобы признать, что все три вида даты в исходных данных - ValidFrom, ValidTo и EventDate - имеет же результата в требуемой мощности. Требуемый вывод - это список диапазонов, но граничные даты этих диапазонов равны и не отличаются от данных источника и данных источника-события.

Таким образом, если предположить такую ​​структуру таблицы и выборки данных:

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 
Смежные вопросы