【问题标题】:Consecutive streak of dates连续的日期
【发布时间】:2010-09-28 11:54:57
【问题描述】:

希望这不是另一个问题的欺骗,但我在其他任何地方都看不到它 - 这也是 another question I asked 的简化版本,希望能让我开始研究如何解决它。

我希望找出每个月至少有一次付款的连续付款范围。

我有以下示例数据

CREATE TABLE #data
(
Contact_reference NVARCHAR(55)
,Date_payment DATETIME
,Payment_value MONEY
)

INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2003-06-08',12.82)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2004-06-08',12.82)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2004-12-08',12.82)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2005-04-08',12.82)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2005-05-08',12.82)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2005-06-08',12.82)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2005-07-08',12.82)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2005-08-08',12.82)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2005-09-08',12.82)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2005-10-10',12.8205)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2005-11-10',12.8205)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2005-12-10',12.8205)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2006-01-10',12.8205)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2006-02-10',12.8205)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2006-02-28',12.8205)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2006-04-12',12.8205)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2006-05-10',19.2308)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2007-06-11',19.2308)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2007-07-10',19.2308)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2007-08-09',19.2308)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2007-09-10',19.2308)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2007-10-09',19.2308)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2007-11-09',19.2308)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2007-12-10',19.2308)
INSERT INTO #data VALUES ('18EC3CD2-3065-4FF4-BE40-000004228590','2008-01-10',19.2308)

我希望能够为每个联系人计算出他们连续给予的范围(定义为每个日历月至少给予一次)、连续付款的次数、每次的总价值范围(如果可能的话,最好是当前范围和最近一个范围的结尾之间的差距)。

对于上面的测试数据,我的输出如下所示:

CREATE TABLE #results
(
contact_reference NVARCHAR(55)
,Range_start DATETIME
,Range_end DATETIME
,Payments INT
,Value MONEY
,months_until_next_payment INT --works out the gap between the range_end date for a group and the range_start date for the next group
)
INSERT INTO #results VALUES('18EC3CD2-3065-4FF4-BE40-000004228590','2003-06-08','2003-06-08',1,12.82,12)
INSERT INTO #results VALUES('18EC3CD2-3065-4FF4-BE40-000004228590','2004-06-08','2004-06-08',1,12.82,6)
INSERT INTO #results VALUES('18EC3CD2-3065-4FF4-BE40-000004228590','2004-12-08','2004-12-08',1,12.82,4)
INSERT INTO #results VALUES('18EC3CD2-3065-4FF4-BE40-000004228590','2005-04-08','2006-02-28',12,153.843,2)
INSERT INTO #results VALUES('18EC3CD2-3065-4FF4-BE40-000004228590','2006-04-12','2008-06-06',27,416.6673,NULL)

我已经使用岛屿或迭代来寻找答案,但坦率地说,我什至不知道从哪里开始将它们应用于我的问题,所以非常感谢任何帮助:)

【问题讨论】:

    标签: sql sql-server sql-server-2005 tsql


    【解决方案1】:

    编辑:我已在months_until_next_payment 列中添加。这在应用程序中比使用自联接更有效,但是因为 SQL Server 没有任何特别令人满意的方式来引用下一行和上一行。

    ;WITH base AS ( 
    SELECT    Contact_reference  ,
              Payment_value,
              DATEPART(YEAR, Date_payment)*12 + DATEPART(MONTH, Date_payment) - 
                   DENSE_RANK() OVER 
                       (PARTITION BY Contact_reference 
                        ORDER BY DATEPART(YEAR, Date_payment)*12 + DATEPART(MONTH, Date_payment)) AS G,
              Date_payment
     FROM     #data
     ),
     cte AS
     (
     SELECT 
              Contact_reference, 
              ROW_NUMBER() over (partition by Contact_reference 
                                     order by MIN(Date_payment)) RN,
              MIN(Date_payment) Range_start,
              MAX(Date_payment) Range_end, 
              COUNT(Payment_value) Payments, 
              SUM(Payment_value) Value
     FROM base
     GROUP BY Contact_reference, G
     )
     SELECT 
           c1.Contact_reference, 
           c1.Payments, 
           c1.Range_end, 
           c1.Range_start, 
           c1.Value, 
           DATEDIFF(month, c1.Range_end,c2.Range_start) months_until_next_payment
     FROM cte c1
     LEFT join cte c2 ON c1.Contact_reference=c2.Contact_reference and c2.RN = c1.RN+1
    

    【讨论】:

    • 很抱歉我完全不知道这一点,但你为什么要做 DATEPART(year) * 12?另外,如果您可以让我知道如何开始为months_until_next 付款进行左连接,那将是非常棒的,因为我是使用SQL 的合适n00b! :) 谢谢!
    • 您可以在最终的 select 语句中添加一个计算列,而不是另一个连接,将其更改为:SELECT Contact_reference, MIN(Date_payment) Range_start,MAX(Date_payment) Range_end, COUNT(Payment_value)付款,SUM(Payment_value) 值,(从 cte1 中选择 MIN(G) - q.g + 1,其中 g > q.g)作为months_until_next_payment FROM cte1 q GROUP BY Contact_reference, G
    • DATEPART(year) * 12 + DATEPART(month) 用于获取一个整数,每次我们提前一个日历月时该整数变化一。因为一个月有 12 年,当我们重新设置为 1 月时,减去 11 表示月份的变化,加上 1*12 表示年份的变化,即净结果是加一。
    • 哇,这个答案太棒了!非常感谢这个:)
    • @Martin Smith,只是为了向您和其他任何人报告,使用 datepart 的解决方案最终成为我需要的确切解决方案。我必须意识到,我需要一个 GROUP 来确定 row_number() 的连续月份。所以,我像你一样把它分成两部分,一:创建组,使用与 datepart 和密集排名相同的语法二:在结果组上创建行号以获得每个组上的行号,分区由小组。 2天的研究,这就是我需要的答案。干杯!
    【解决方案2】:

    您可以使用光标来完成。像 c#/java 这样的语言是解决这个问题的更好选择。

    DECLARE @date DATETIME
    DECLARE @nextDate DATETIME
    DECLARE @rangeStart DATETIME
    DECLARE @rangeEnd DATETIME
    
    DECLARE @value decimal(18,2)
    DECLARE @valueSum decimal(18,2)
    DECLARE @count int
    
    DECLARE @PaymentCursor CURSOR
    
    SET @PaymentCursor = CURSOR FOR
        SELECT Date_payment, Payment_value FROM #data
        ORDER BY Date_payment
    
    OPEN @PaymentCursor
        FETCH NEXT FROM @PaymentCursor INTO @nextDate, @value   
        SET @date = @nextDate
        SET @rangeStart = @nextDate
        SET @valueSum = 0
        SET @count = 0
    
        WHILE (@@FETCH_STATUS = 0)
        BEGIN       
            FETCH NEXT FROM @PaymentCursor INTO @nextDate, @value
    
            SET @count = @count + 1
            SET @valueSum = @valueSum + @value      
    
            IF (DATEDIFF(mm, @date, @nextDate) > 1)
            BEGIN
                SELECT @rangeStart AS RangeStart, @date AS RangeEnd, @count AS Coount, @valueSum AS VALUE, DATEDIFF(mm, @date, @nextDate) AS months_until_next_payment
                SET @valueSum = 0               
                SET @count = 0
                SET @rangeStart = @nextDate
            END
    
            SET @date = @nextDate           
        END
    
        SELECT @rangeStart AS RangeStart, @date AS RangeEnd, @count AS Coount, @valueSum AS VALUE, null AS months_until_next_payment
    
    CLOSE @PaymentCursor
    DEALLOCATE @PaymentCursor
    

    【讨论】:

    • 如果您必须在 SQL 中使用游标,您可能应该考虑将逻辑放入您的应用程序而不是数据库中。
    • 谢谢,但它会将每一行生成为一个单独的结果表(我希望将它们全部放在一起以便于参考)并且它似乎运行得很慢(大约需要 6 分钟才能拉出 10记录,我有好几十万)。
    • 您可以在表中插入结果行,创建表并在 WHILE 中使用 INSERT INTO 而不是 SELECT。性能取决于您的数据和硬件以及使用 CURSOR 会导致性能损失......
    猜你喜欢
    • 2021-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多