【问题标题】:Get Monthly Totals from Running Totals从运行总计中获取每月总计
【发布时间】:2013-08-08 14:30:12
【问题描述】:

我在 SQL Server 2008 数据库中有一个表,其中有两列保存运行总计,称为 Hours 和 Starts。另一列 Date 保存记录的日期。日期在任何给定月份中都是零星的,但总有一个记录在该月的最后一小时。

例如:

ContainerID | Date             | Hours | Starts

1           | 2010-12-31 23:59 | 20    | 6
1           | 2011-01-15 00:59 | 23    | 6
1           | 2011-01-31 23:59 | 30    | 8

2           | 2010-12-31 23:59 | 14    | 2
2           | 2011-01-18 12:59 | 14    | 2
2           | 2011-01-31 23:59 | 19    | 3

如何查询表以获取两个指定年份之间每个月的总小时数和开始数? (在这种情况下是 2011 年和 2013 年。)我知道我需要从一个月的最后一条记录中获取值,然后减去上个月最后一条记录中的值。但是,我很难想出一个在 SQL 中执行此操作的好方法。

根据要求,以下是预期结果:

ContainerID | Date             | MonthlyHours | MonthlyStarts
1           | 2011-01-31 23:59 | 10           | 2
2           | 2011-01-31 23:59 | 5            | 1

【问题讨论】:

  • 29:59?一天多工作 6 小时就好了。
  • 您能否为上述示例添加预期结果?
  • 不开玩笑...我纠正了错字。
  • 你也有,我猜是错字,在第三行 - ContainerID = 2 ?
  • 是的,我更正了它并添加了我对示例的期望。

标签: sql sql-server sql-server-2008


【解决方案1】:

试试这个:

SELECT  c1.ContainerID, 
        c1.Date, 
        c1.Hours-c3.Hours AS "MonthlyHours", 
        c1.Starts - c3.Starts AS "MonthlyStarts"
FROM Containers c1
LEFT OUTER JOIN Containers c2 ON
                                c1.ContainerID = c2.ContainerID
                            AND datediff(MONTH, c1.Date, c2.Date)=0
                            AND c2.Date > c1.Date
    LEFT OUTER JOIN Containers c3 ON
                                    c1.ContainerID = c3.ContainerID
                                AND datediff(MONTH, c1.Date, c3.Date)=-1
    LEFT OUTER JOIN Containers c4 ON
                                    c3.ContainerID = c4.ContainerID
                                AND datediff(MONTH, c3.Date, c4.Date)=0
                                AND c4.Date > c3.Date
    WHERE 
            c2.ContainerID is null
        AND c4.ContainerID is null
        AND c3.ContainerID is not null
ORDER BY c1.ContainerID, c1.Date

【讨论】:

    【解决方案2】:

    使用 recursive CTE 和一些“创造性”的 JOIN 条件,您可以获取每个 ContainerID 下个月的值:

    WITH CTE_PREP AS 
    (
        --RN will be 1 for last row in each month for each container 
        --MonthRank will be sequential number for each subsequent month (to increment easier)
        SELECT 
            *
            ,ROW_NUMBER() OVER (PARTITION BY ContainerID, YEAR(Date), MONTH(DATE) ORDER BY Date DESC) RN
            ,DENSE_RANK() OVER (ORDER BY YEAR(Date),MONTH(Date)) MonthRank
        FROM Table1
    )
    , RCTE AS 
    (
        --"Zero row", last row in decembar 2010 for each container
        SELECT *, Hours AS MonthlyHours, Starts AS MonthlyStarts
        FROM CTE_Prep
        WHERE YEAR(date) = 2010 AND MONTH(date) = 12 AND RN = 1 
    
        UNION ALL
    
        --for each next row just join on MonthRank + 1
        SELECT t.*, t.Hours - r.Hours, t.Starts - r.Starts
        FROM RCTE r
        INNER JOIN CTE_Prep t ON r.ContainerID = t.ContainerID AND r.MonthRank + 1 = t.MonthRank AND t.Rn = 1
    )
    SELECT ContainerID, Date, MonthlyHours, MonthlyStarts
    FROM RCTE
    WHERE Date >= '2011-01-01' --to eliminate "zero row"
    ORDER BY ContainerID
    

    SQLFiddle DEMO(我添加了 2 月和 3 月的一些数据,以便测试不同月份的长度)

    Old version fiddle

    【讨论】:

    • 由于这适用于您的 SQLFiddle 演示,因此我将其标记为答案。它本来对我有用,但我发现不同容器中的数据在不同程度上是混乱的(例如,某些容器有重复的记录、月末时间不一致),这使得通过 SQL 查询获得我需要的东西是不可行的。
    • @Matt 我可以重写查询,使其始终使用每个月的最后一条记录,而不考虑实际时间和重复项 - 如果对您有帮助吗?
    • @Matt 好的。我已经用新版本编辑了答案。无论如何,这是一个比使用每月最后一分钟更好的解决方案。我不知道为什么我一开始就没有想到。
    • 我想到了什么 - 如果有几个月没有容器数据 - 查询可能需要一些调整才能将它们计为 0。
    • 忽略上个月没有信息的月份会更有意义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-07
    • 2016-11-16
    相关资源
    最近更新 更多