T-SQL 递归,基于先前迭代的日期偏移

T-SQL recursion, date shifting based on previous iteration

我有一个数据集,其中包括客户、付款日期以及他们付款的天数。我需要计算每笔付款涵盖的 start/end 日期。如果在当前承保期结束之前付款,这会很困难。

我想出的最好的思考方式是按月计算 phone 计划,客户可以在给定月份的任何时间点支付指定天数的费用。下一个承保期应始终在上一个承保期到期后的第二天开始。

这是使用临时文件的代码示例 table。

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);

我需要获取 start/end 的报道日期。

首期付款于 2018-01-01 支付,他们支付了 30 天。这意味着它们在 2018 年 1 月 30 日之前都在承保范围内(Payment_Date + Paid_Days - 1,因为付款日期包含在承保日期内)。但是他们在 2018-01-29 进行了下一次付款,所以我需要计算下一次承保范围的开始日期 window,在本例中为之前的 Payment_Date + 之前的 Paid_Days .在这种情况下,保险范围 window 2 从 2018-02-01 开始,并将延续到 2018-02-19,因为他们只在 Payment_Date 2018-01-29 支付了 20 天的费用。

预期输出为:

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 相加并将其添加到第一笔付款的开始日期,但是这仅适用于在之前的承保范围到期之前付款的情况。此外,我需要计算每个 Payment_Date 的覆盖率 start/end 日期。

最后,使用LAG/LEAD函数似乎不起作用,因为它不考虑前一次迭代的结果,只考虑前一条记录的当前值。使用 LAG/LEAD,您得到了第二次付款记录的正确答案,但不是第三次。

有没有办法用递归 CTE 做到这一点?

注意:这不是递归解决方案,但它是基于集合的,而不是您的循环解决方案。

虽然试图递归地解决这个问题,但我突然意识到这本质上是一个 "running totals" 问题,可以使用 window 函数轻松解决。

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 的 "running total" 加在一起。使用 LAG 获取以前的记录结束日期+1 得到开始日期。 COALESCE 是处理第一条记录。对于不止一个客户,您可以PARTITION BY Customer_Id.

所以当然,在发布这篇文章之后,我遇到了一个已经回答过的类似问题。

这是 link:

基于该解决方案,我能够针对我自己的问题构建以下解决方案。

这里的关键是添加 "prep_data" CTE,这使得递归问题变得更加容易。

 ;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;