如何编写 SQL 语句以每两个相邻月的同一天使用 group 对数据求和

How to write a SQL statement to sum data using group by the same day of every two neighboring months

我有这样的数据table:

datetime       data 
-----------------------
...
2017/8/24       6.0
2017/8/25       5.0
...
2017/9/24       6.0
2017/9/25       6.2
...
2017/10/24      8.1
2017/10/25      8.2

我想写一个SQL语句来在特定时间范围内每两个相邻月份的24日使用组对数据求和,例如:从2017/7/20到2017/10/25如上

这个SQL语句怎么写?我正在使用 SQL Server 2008 R2。

预期的结果table是这样的:

datetime_range          data_sum
------------------------------------
...
2017/8/24~2017/9/24       100.9
2017/9/24~2017/10/24      120.2
...

这里进行的一种概念性方法是将 "month" 重新定义为在每个正常月份的 24 日结束。使用 SQL 服务器月份函数,我们会将 24 日之后发生的任何日期指定为属于下一个月。然后我们可以按年份和这个移位的月份进行汇总以获得数据总和。

WITH cte AS (
    SELECT
        data,
        YEAR(datetime) AS year,
        CASE WHEN DAY(datetime) > 24
             THEN MONTH(datetime) + 1 ELSE MONTH(datetime) END AS month
    FROM yourTable
)

SELECT
    CONVERT(varchar(4), year) + '/' + CONVERT(varchar(2), month) +
        '/25~' +
    CONVERT(varchar(4), year) + '/' + CONVERT(varchar(2), (month + 1)) +
        '/24' AS datetime_range,
    SUM(data) AS data_sum
FROM cte
GROUP BY
    year, month;

请注意,您建议的范围似乎包括两端的第 24 位,从会计的角度来看这没有意义。我假设这个月包括并结束于 24 日(即 25 日是下一个会计期间的第一天。

Demo

我认为最简单的方法是减去 25 天并按月汇总:

select year(dateadd(day, -25, datetime)) as yr,
       month(dateadd(day, -25, datetime)) as mon,
       sum(data)
from t
group by dateadd(day, -25, datetime);

您可以格式化 yrmon 以获取特定范围的日期,但这会进行聚合(并且 yr/mon 列可能是够了)。

第 0 步:构建日历 table。每个数据库最终都需要一个日历 table 来简化这种计算。

在此 table 中,您可能有以下列:

  • 日期(主键)
  • 月份
  • 季度
  • 半年(例如 1 或 2)
  • 一年中的第几天(1 到 366)
  • 星期几(数字或文本)
  • 是周末(现在看起来多余,但以后可以节省大量时间)
  • 会计年度 quarter/year(如果贵公司的会计年度不是从 1 月 1 日开始)
  • 是假期
  • 等等

如果您的公司在 24 日开始其月份,那么您可以添加代表该日期的 "Fiscal Month" 列。

第 1 步:加入日历 table

第 2 步:按日历中的列分组 table。


日历 table 起初听起来很奇怪,但一旦你意识到它们实际上很小,即使它们跨越几百年,它们很快就会成为一项重要资产。

不要试图通过使用计算列来节省磁盘 space。您需要真正的列,因为它们更快并且可以在必要时建立索引。 (老实说,通常仅 PK 索引就足以满足更宽的日历 tables。)

我建议动态构建一些日期范围行,这样您就可以将数据加入到那些行中进行聚合,就像这个例子:

+----+---------------------+---------------------+----------------+
|    |   period_start_dt   |    period_end_dt    | your_data_here |
+----+---------------------+---------------------+----------------+
|  1 | 24.04.2017 00:00:00 | 24.05.2017 00:00:00 |              1 |
|  2 | 24.05.2017 00:00:00 | 24.06.2017 00:00:00 |              1 |
|  3 | 24.06.2017 00:00:00 | 24.07.2017 00:00:00 |              1 |
|  4 | 24.07.2017 00:00:00 | 24.08.2017 00:00:00 |              1 |
|  5 | 24.08.2017 00:00:00 | 24.09.2017 00:00:00 |              1 |
|  6 | 24.09.2017 00:00:00 | 24.10.2017 00:00:00 |              1 |
|  7 | 24.10.2017 00:00:00 | 24.11.2017 00:00:00 |              1 |
|  8 | 24.11.2017 00:00:00 | 24.12.2017 00:00:00 |              1 |
|  9 | 24.12.2017 00:00:00 | 24.01.2018 00:00:00 |              1 |
| 10 | 24.01.2018 00:00:00 | 24.02.2018 00:00:00 |              1 |
| 11 | 24.02.2018 00:00:00 | 24.03.2018 00:00:00 |              1 |
| 12 | 24.03.2018 00:00:00 | 24.04.2018 00:00:00 |              1 |
+----+---------------------+---------------------+----------------+

DEMO

declare @start_dt date;
set @start_dt = '20170424';

select
       period_start_dt, period_end_dt, sum(1) as your_data_here
from (
        select 
               dateadd(month,m.n,start_dt)   period_start_dt
             , dateadd(month,m.n+1,start_dt) period_end_dt
        from (
               select @start_dt start_dt ) seed
        cross join (
                    select 0 n union all
                    select 1 union all
                    select 2 union all
                    select 3 union all
                    select 4 union all
                    select 5 union all
                    select 6 union all
                    select 7 union all
                    select 8 union all
                    select 9 union all
                    select 10 union all
                    select 11
                   ) m
     ) r
-- LEFT JOIN YOUR DATA
-- ON yourdata.date >= r.period_start_dt and data.date < r.period_end_dt
group by
       period_start_dt, period_end_dt      

在加入您的数据时,请不要试图使用 "between"。请按照上面的说明使用 yourdata.date >= r.period_start_dt and data.date < r.period_end_dt 否则您可能会重复计算信息,因为 between 包括下限和上限。