Объединить непрерывные диапазоны дат с одинаковым идентификатором и одинаковым количеством - PullRequest
1 голос
/ 06 ноября 2019

У меня есть следующий набор данных, в котором у человека есть идентификатор, и его количество может меняться со временем, поэтому изменения записываются как диапазоны дат. Иногда в диапазонах дат есть пробелы, в которых нет доступной информации. Это хорошо. Однако я хочу объединить непрерывные диапазоны дат, где они имеют одинаковое количество, в то время как оно охватывает несколько записей, таких как строки 2 и 3, для идентификатора = 1.

DECLARE @DataTable TABLE (
    ID [int] NULL,
    StartDate [date] NULL,
    EndDate [date] NULL,
    Amount [decimal](12,2) NULL
)

INSERT INTO @DataTable
SELECT 1, '20180101','20180513', 10.00 UNION ALL
SELECT 1, '20180630','20190301', 15.00 UNION ALL
SELECT 1, '20190302','20190615', 15.00 UNION ALL
SELECT 1, '20190616','20991231', 5.00 UNION ALL
SELECT 2, '20190101','20190331', 35.00 UNION ALL
SELECT 2, '20190401','20191031', 30.00  UNION ALL
SELECT 3, '20180505','20180930', 19.00 UNION ALL
SELECT 3, '20181001','20190228', 1.00 UNION ALL
SELECT 3, '20190501','20190815', 1.00 UNION ALL
SELECT 3, '20190819','20190827', 5.00 UNION ALL
SELECT 3, '20190828','20991231', 1.00 UNION ALL
SELECT 4, '2017-10-01', '2017-12-31',   688.96 UNION ALL
SELECT 4, '2018-01-01', '2018-04-30',   707.96 UNION ALL
SELECT 4, '2018-05-01', '2018-05-31',   783.96 UNION ALL
SELECT 4, '2018-06-01', '2018-12-31',   707.96 UNION ALL
SELECT 4, '2019-01-01', '2019-03-31',   707.96 UNION ALL
SELECT 4, '2019-04-01', '2019-04-30',   571.46 UNION ALL
SELECT 4, '2019-05-01', '2019-06-30',   707.96 UNION ALL
SELECT 4, '2019-07-01', '2099-12-31',   707.96
;

Image of table

Я решил эту проблему, сгенерировав строки дат между начальной и конечной датами, используя dimDate, а затем вел запись, в которой либо сумма изменилась по сравнению с предыдущей записью, либо был пробел дат для идентификатора. Затем я использовал следующую доступную дату записи, чтобы использовать ее в качестве даты окончания. Запрос выглядит следующим образом:

WITH DateList AS (
SELECT DT.*, DD.DateOnly AS RecordDate
FROM @DataTable DT
INNER JOIN dimDate DD ON DT.StartDate <= DD.DateOnly AND CASE WHEN DT.EndDate > GETDATE() THEN CONVERT(DATE,GETDATE()) ELSE DT.EndDate END >= DD.DateOnly
)

, PrevValue AS (
SELECT 
    *
    , LAG(RecordDate) OVER (PARTITION BY ID ORDER BY RecordDate) AS PrevDate
    , LAG(Amount) OVER (PARTITION BY ID ORDER BY RecordDate) AS PrevAmt
FROM DateList 
)
, KeepHistory AS (
SELECT 
    *
FROM PrevValue
WHERE Amount <> PrevAmt OR PrevAmt IS NULL OR DATEADD(DAY,1,PrevDate) <> RecordDate OR PrevDate IS NULL
)
, FINAL AS (
SELECT 
    *
    , LEAD(PrevDate) OVER (PARTITION BY ID ORDER BY StartDate) AS NextEndDate
FROM KeepHistory
)

SELECT 
    ID 
    , StartDate
    , CASE WHEN NextEndDate > EndDate THEN NextEndDate ELSE EndDate END AS EndDate
    , Amount
FROM FINAL 

Final result with row 2 and 3 are merged into 2

Мой вопрос, есть ли альтернативный способ подойти к этому без прохождения dimDate или генерации дат между начальной и конечной датой. Можно ли этого добиться, просто используя оконные функции, такие как разрыв и проблема островов, например здесь ?

Пожалуйста, дайте мне знать, если у вас возникнут проблемы с моим текущим решением. Спасибо.

Что касается ответа @Larnu, ваш обновленный запрос работает для большинства. Я добавил пример ID = 4, который, кажется, вызывает проблему при объединении 4-й и 5-й строк. Он также объединяет 7-й ряд, когда 6-й ряд имеет разное количество.

ID = 4 4-е, 5-е и 7-е объединены;6-й имеет различное количество

Ответы [ 2 ]

1 голос
/ 06 ноября 2019

Это то, что вам нужно?

WITH Gaps AS(
    SELECT DT.ID,
           DT.StartDate,
           DT.EndDate,
           DT.Amount,
           DATEDIFF(DAY,LAG(DATEADD(DAY,1,DT.EndDate),1,DT.StartDate) OVER (PARTITION BY DT.ID, DT.Amount ORDER BY DT.StartDate ASC), DT.StartDate) AS Gap
    FROM @DataTable DT),
Grps AS(
    SELECT G.ID,
           G.StartDate,
           G.EndDate,
           G.Amount,
           ROW_NUMBER() OVER (PARTITION BY G.ID ORDER BY G.StartDate) - 
           ROW_NUMBER() OVER (PARTITION BY G.ID,Amount ORDER BY G.StartDate) + Gap AS Grp
    FROM Gaps G)
SELECT G.ID,
       MIN(G.StartDate) AS StartDate,
       MAX(EndDate) AS EndDate,
       G.Amount
FROM Grps G
GROUP BY G.ID,
         G.Amount,
         G.Grp
ORDER BY ID,
         StartDate;

Добавлен уникальный идентификатор, чтобы обойти "особенность":

DECLARE @DataTable TABLE (
    UniqueID int IDENTITY(1,1),
    ID [int] NULL,
    StartDate [date] NULL,
    EndDate [date] NULL,
    Amount [decimal](12,2) NULL
)

INSERT INTO @DataTable
SELECT 1, '20180101','20180513', 10.00 UNION ALL
SELECT 1, '20180630','20190301', 15.00 UNION ALL
SELECT 1, '20190302','20190615', 15.00 UNION ALL
SELECT 1, '20190616','20991231', 5.00 UNION ALL
SELECT 2, '20190101','20190331', 35.00 UNION ALL
SELECT 2, '20190401','20191031', 30.00  UNION ALL
SELECT 3, '20180505','20180930', 19.00 UNION ALL
SELECT 3, '20181001','20190228', 1.00 UNION ALL
SELECT 3, '20190501','20190815', 1.00 UNION ALL
SELECT 3, '20190819','20190827', 5.00 UNION ALL
SELECT 3, '20190828','20991231', 1.00 UNION ALL
SELECT 4, '20171001', '20171231',   688.96 UNION ALL
SELECT 4, '20180101', '20180430',   707.96 UNION ALL
SELECT 4, '20180501', '20180531',   783.96 UNION ALL
SELECT 4, '20180601', '20181231',   707.96 UNION ALL
SELECT 4, '20190101', '20190331',   707.96 UNION ALL
SELECT 4, '20190401', '20190430',   571.46 UNION ALL
SELECT 4, '20190501', '20190630',   707.96 UNION ALL
SELECT 4, '20190701', '20991231',   707.96;

--SELECT *
--FROM @DataTable;

WITH Gaps AS(
    SELECT DT.UniqueID,
           DT.ID,
           DT.StartDate,
           DT.EndDate,
           DT.Amount,
           DATEDIFF(DAY,LAG(DATEADD(DAY,1,DT.EndDate),1,DT.StartDate) OVER (PARTITION BY DT.ID, DT.Amount ORDER BY DT.UniqueID ASC), DT.StartDate) AS Gap
    FROM @DataTable DT),
Grps AS(
    SELECT G.UniqueID,
           G.ID,
           G.StartDate,
           G.EndDate,
           G.Amount,
           G.Gap,
           ROW_NUMBER() OVER (PARTITION BY G.ID ORDER BY G.UniqueID) - 
           ROW_NUMBER() OVER (PARTITION BY G.ID,Amount ORDER BY G.UniqueID) + (Gap * UniqueID) AS Grp
    FROM Gaps G)
SELECT G.ID,
       MIN(G.StartDate) AS StartDate,
       MAX(EndDate) AS EndDate,
       G.Amount
FROM Grps G
GROUP BY G.ID,
         G.Amount,
         G.Grp
ORDER BY ID,
         StartDate;
0 голосов
/ 06 ноября 2019

Вы можете использовать рекурсию следующим образом

    DECLARE @DataTable TABLE (
        ID [int] NULL,
        StartDate [date] NULL,
        EndDate [date] NULL,
        Amount [decimal](12,2) NULL
    )

    INSERT INTO @DataTable
    SELECT 1, '20180101','20180513', 10.00 UNION ALL
    SELECT 1, '20180630','20190301', 15.00 UNION ALL
    SELECT 1, '20190302','20190615', 15.00 UNION ALL
    SELECT 1, '20190616','20991231', 5.00 UNION ALL
    SELECT 2, '20190101','20190331', 35.00 UNION ALL
    SELECT 2, '20190401','20191031', 30.00  UNION ALL
    SELECT 3, '20180505','20180930', 19.00 UNION ALL
    SELECT 3, '20181001','20190228', 1.00 UNION ALL
    SELECT 3, '20190501','20190815', 1.00 UNION ALL
    SELECT 3, '20190819','20190827', 5.00 UNION ALL
    SELECT 3, '20190828','20991231', 1.00;


    ;with cte as(
    select t1.id,
t1.startDate,
t1.EndDate,
t1.Amount,
     row_number() over (partition by t1.id order by t1.Id,t1.startDate) R#  
from @DataTable t1
    left outer join @DataTable t2 on dateadd(d,-1,t1.startdate)=t2.enddate and t1.Amount=t2.Amount
    and t1.id=t2.id
    where t2.amount is null),
    cte1 as
    (select 
t1.id,
t1.startDate,
t1.EndDate,
t1.Amount,
t1.R# from cte t1

    union all

    select t1.id,
    t2.startdate,
t1.endDate, 
t1.amount,
R# from @DataTable t1 inner join cte1 t2
    on dateadd(d,-1,t1.startdate)=t2.enddate and t1.Amount=t2.Amount)
    select id,
min(stArtdate)[startdate],
max(enddate)[enddate],
min(amount)[amount] from cte1 GROUP by R#,id 
    Order  by id,max(enddate)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...