T-SQL递归,基于先前迭代的日期转换

时间:2019-02-27 18:28:22

标签: sql-server tsql recursive-query

我有一个数据集,其中包括客户,付款日期以及他们已付款的天数。我需要计算每次付款涵盖的承保范围开始/结束日期。当在当前保险期结束之前付款时,这很困难。

我想到的最好的方法是一个月到一个月的手机套餐,客户可以在给定月份的任何时间支付指定天数。下一个涵盖期应始终在上一个涵盖期到期后的第二天开始。

这是使用临时表的代码示例。

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年1月1日付款,付款期限为30天。这意味着他们的保险期限为2018年1月30日(Payment_Date + Paid_Days-1,因为付款日期已包括在内)。但是他们下一次付款是在2018年1月29日,因此我需要计算下一个覆盖范围窗口的开始日期,在这种情况下,这将是上一个Payment_Date +上一个Paid_Days。在这种情况下,覆盖范围窗口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的承保范围开始/结束日期。

最后,使用LAG / LEAD函数似乎不起作用,因为它不考虑先前迭代的结果,而仅考虑先前记录的当前值。使用LAG / LEAD,您可以获得第二笔付款记录的正确答案,而不是第三笔。

是否可以通过递归CTE做到这一点?

2 个答案:

答案 0 :(得分:2)

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

在尝试递归解决此问题时,让我感到这本质上是一个"running totals"问题,可以通过窗口函数轻松解决。

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

答案 1 :(得分:0)

因此,当然,在发布此消息后,我遇到了一个已经回答的类似问题。

以下是链接:Recursively retrieve LAG() value of previous record

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

此处的关键是添加“ 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;