Рекурсия T-SQL, смещение даты на основе предыдущей итерации - PullRequest
0 голосов
/ 27 февраля 2019

У меня есть набор данных, который включает клиента, дату платежа и количество дней, за которые он заплатил.Мне нужно рассчитать даты начала / окончания покрытия, которые покрывает каждый платеж.Это сложно, когда платеж производится до окончания текущего периода покрытия.

Лучший способ подумать об этом - ежемесячный тариф на сотовый телефон, когда клиент может заплатить за определенное количество дней в любой момент в течение данного месяца.Следующий покрываемый период всегда должен начинаться на следующий день после истечения срока действия предыдущего покрываемого периода.

Вот пример кода с использованием временной таблицы.

CREATE TABLE #Payments 
(Customer_ID INTEGER, 
 Payment_Date DATE,
 Days_Paid INTEGER);

INSERT INTO #Payments
VALUES (1,'2018-01-01',30);

INSERT INTO #Payments
VALUES (1,'2018-01-29',20);

INSERT INTO #Payments
VALUES (1,'2018-02-15',30);

INSERT INTO #Payments
VALUES (1,'2018-04-01',30);

Мне нужно получить начало / конец покрытиявосходит

Первоначальный платеж был сделан 2018-01-01, и они заплатили за 30 дней.Это означает, что они покрываются до 2018-01-30 (Payment_Date + Paid_Days - 1, поскольку дата платежа включена в покрытый день).Однако они произвели следующий платеж 2018-01-29, поэтому мне нужно рассчитать дату начала следующего окна покрытия, которая в этом случае будет предыдущей Payment_Date + previous Paid_Days.В этом случае окно покрытия 2 начинается с 2018-02-01 и продлится до 2018-02-19, так как они платили только за 20 дней с Payment_Date 2018-01-29.

Ожидаемый результат:

Customer_ID | Payment_Date | Days_Paid | Coverage_Start_Date | Coverage_End_Date
--------------------------------------------------------------------------------
1           |  '2018-01-01'|        30 |         '2018-01-01'|      '2018-01-30'
1           |  '2018-01-29'|        20 |         '2018-01-31'|      '2018-02-19' 
1           |  '2018-02-15'|        30 |         '2018-02-20'|      '2018-03-21'
1           |  '2018-04-01'|        30 |         '2018-04-01'|      '2018-04-30'

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

У меня есть способ сделать это в цикле while, но я хотел бы завершить его с помощью рекурсивного CTE.Я также подумал о том, чтобы просто добавить Days_Paid и добавить его к дате начала первого платежа, однако это работает только в том случае, если платеж сделан до истечения срока действия предыдущего покрытия.Кроме того, мне нужно рассчитать даты начала / окончания покрытия для каждой даты платежа.

Наконец, использование функций LAG / LEAD, похоже, не работает, поскольку не учитывает результат предыдущей итерации, а только текущее значение предыдущей записи.Используя LAG / LEAD, вы получите правильный ответ для 2-й платежной записи, но не для третьей.

Есть ли способ сделать это с помощью рекурсивного CTE?

Ответы [ 2 ]

0 голосов
/ 27 марта 2019

Так что, конечно, сразу после публикации я наткнулся на похожий вопрос, на который уже был дан ответ.

Вот ссылка: Рекурсивно получить значение LAG () предыдущей записи

На основе этого решения я смог построить следующее решение для моего собственного вопроса.

Ключом здесь было добавление CTE "prep_data", что значительно облегчило проблему рекурсии.

 ;WITH prep_data AS
        (SELECT Customer_ID,
                ROW_NUMBER() OVER (PARTITION BY Customer_ID ORDER BY Payment_Date) AS payment_seq_num,
                Payment_Date,
                Days_Paid,
                Payment_Date as Coverage_Start_Date,
                DATEADD(DAY,Days_Paid-1,Payment_Date) AS Coverage_End_Date
          FROM #Payments),
    recursion AS
        (SELECT Customer_ID,
                payment_seq_num,
                Payment_Date,
                Days_Paid,
                Coverage_Start_Date,
                Coverage_End_Date
           FROM prep_data
           WHERE payment_seq_num = 1
           UNION ALL
         SELECT r.Customer_ID,
                p.payment_seq_num,
                p.Payment_Date,
                p.Days_Paid,
                CASE WHEN r.Coverage_End_Date >= p.Payment_Date THEN DATEADD(DAY,1,r.Coverage_End_Date) ELSE p.Payment_Date END AS Coverage_Start_Date,
                DATEADD(DAY,p.Days_Paid-1,CASE WHEN r.Coverage_End_Date >= p.Payment_Date THEN DATEADD(DAY,1,r.Coverage_End_Date) ELSE p.Payment_Date END) AS Coverage_End_Date
           FROM recursion r 
                JOIN prep_data p ON r.payment_seq_num + 1 =p.payment_seq_num
        )
SELECT Customer_ID, 
       Payment_Date, 
       Days_Paid, 
       Coverage_Start_Date, 
       Coverage_End_Date 
  FROM recursion
ORDER BY payment_seq_num;
0 голосов
/ 27 февраля 2019

ПРИМЕЧАНИЕ: Это не рекурсивное решение, но оно основано на множестве по сравнению с решением вашего цикла.

При попытке рекурсивно решить эту проблему, я понял, что это по сути «промежуточные итоги» проблема, и ее можно легко решить с помощью оконных функций.

WITH runningTotal AS 
(
    SELECT p.*, SUM(Days_Paid) OVER(ORDER BY p.Payment_Date) AS runningTotalDays, MIN(Payment_Date) OVER(ORDER BY p.Payment_Date) startDate
    FROM #Payments p 
)
SELECT r.Customer_Id, r.Payment_Date,Days_Paid, COALESCE(DATEADD(DAY, LAG(runningTotalDays) OVER(ORDER BY r.Payment_Date) +1, startDate), startDate) AS Coverage_Start_Date, DATEADD(DAY, runningTotalDays, startDate) AS Coverage_End_Date
FROM runningTotal r

Каждая конечная дата - это «промежуточная сумма» всех предыдущих Days_Paid, добавленных вместе.Использование LAG для получения даты окончания предыдущих записей + 1 возвращает дату начала.COALESCE предназначен для обработки первой записи.Для более чем одного клиента вы можете PARTITION BY Customer_Id.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...