SQL :给定年份和月份,我如何计算每周的订单数量

SQL : given a year and month how can I count the number of orders per WEEK

我的任务是返回指定年份和月份内的订单数量 'per week'(这一切都发生在 SSMS 中)。

我的数据看起来像这样:

OrderId DateCreated
1 2021-12-04 06:01:14.6333333
2 2021-12-04 07:01:14.6333333
3 2021-12-24 00:00:00.0000000
4 2021-12-31 06:01:14.6333333
5 2021-12-31 06:01:14.6333333

我想让结果 table 看起来像这样:

Week OrdersCount
1 1
2 0
3 0
4 1
5 2

目前我有以下 SQL 存储过程,它以年 (@year) 和月 (@month) 作为参数:

SELECT
    SUM(CASE WHEN DateCreated BETWEEN (DATEFROMPARTS(@year, @month, 01)) AND (DATEFROMPARTS(@year, @month, 07)) 
             THEN 1 ELSE 0 END) AS Week1,
    SUM(CASE WHEN DateCreated BETWEEN (DATEFROMPARTS(@year, @month, 08)) AND (DATEFROMPARTS(@year, @month, 14)) 
             THEN 1 ELSE 0 END) AS Week2,
    SUM(CASE WHEN DateCreated BETWEEN (DATEFROMPARTS(@year, @month, 15)) AND (DATEFROMPARTS(@year, @month, 21)) 
             THEN 1 ELSE 0 END) AS Week3,
    SUM(CASE WHEN DateCreated BETWEEN (DATEFROMPARTS(@year, @month, 22)) AND (DATEFROMPARTS(@year, @month, 28)) 
             THEN 1 ELSE 0 END) AS Week4,
    SUM(CASE WHEN DateCreated BETWEEN (DATEFROMPARTS(@year, @month, 29)) AND (DATEFROMPARTS(@year, @month, 29)) 
             THEN 1 ELSE 0 END) AS Week5
FROM
    dbo.Orders

上面的语句 returns 接近我需要的东西但是有一些问题,我的结果集是这样的:

wk1 wk2 wk3 wk4 wk5
1 1 0 0 1 0

所以最大的问题当然是第 5 周的方向和缺失订单。我的周数是沿 x 轴而不是 y 轴显示的,但似乎也是因为 EOMONTH() 函数默认时间戳为午夜,不考虑该月最后一天凌晨 12 点之后的任何订单。

根据我迄今为止所做的研究,我认为我应该使用 DATEADDDATEDIFFCOUNT 的某种组合(而不是 SUM,这样我就可以做 GROUP BY) 我很了解这些 functions/statements 如何独立工作,但无法将它们组合在一起以实现我的目标。任何和所有帮助将不胜感激!

我假设:

  1. 您希望第 1 周从每月的第一天开始计算并且始终是前 7 天,并且
  2. 第 5 周应始终是部分周(即 28 日之后的一个月的末尾),除非在非闰年的 2 月不存在。
  3. 您想在结果集中看到零周。

如果所有这些假设都是正确的,我会使用这样的东西:

DECLARE @Weeks TABLE (Week INT IDENTITY(1,1), SD DATETIME, ED DATETIME)
DECLARE @Date DATETIME = DATEFROMPARTS(@year,@month,1)
WHILE @Date < EOMONTH(DATEFROMPARTS(@year,@month,1))
BEGIN
    INSERT INTO @Weeks (SD, ED) 
        SELECT @Date, CASE WHEN DATEADD(DAY,7,@Date) > DATEADD(MONTH,1,DATEFROMPARTS(@year,@month,1)) THEN DATEADD(MONTH,1,DATEFROMPARTS(@year,@month,1)) ELSE DATEADD(DAY,7,@Date) END
    SET @Date = DATEADD(DAY,7,@Date)
END

SELECT w.Week, COUNT(ID) 'OrdersCount'
FROM @Weeks w 
    LEFT JOIN dbo.Orders ON w.SD <= DateCreated AND w.ED > DateCreated
GROUP BY w.Week

我使用 table 变量首先构建了一个周列表 - 这可能不是绝对必要的(还有其他方法可以实现),但我喜欢这种流程。我建议您使用 < 和下一个时期开始的组合作为结束日期标准,因为 BETWEEN 始终包含在内并且在这种用例中容易出现包含时间部分的日期问题。

如果您不需要在所有情况下都从每月的第一天开始计算周数,并且不介意不会表示零周,那么一个非常简单的解决方案是使用 DATEPART(周...),例如:

SELECT DATEPART(WEEK,DateCreated)-DATEPART(WEEK,DATEFROMPARTS(@year,@month,1))+1 'Week', COUNT(ID) 'CountID' 
    FROM dbo.Orders 
    WHERE DateCreated >= DATEFROMPARTS(@year,@month,1) AND DateCreated < DATEADD(MONTH,1,DATEFROMPARTS(@year,@month,1))
    GROUP BY DATEPART(WEEK,DateCreated)

您可以使用 CASE 获取单个周数,然后按该数分组。

使用CROSS APPLY (VALUES避免重复代码

SELECT
    v.WeekNumber,
    TotalOrders = COUNT(*)
FROM
    dbo.Orders
CROSS APPLY (VALUES (
    CASE WHEN DateCreated >= DATEFROMPARTS(@year, @month, 1) AND DateCreated < DATEFROMPARTS(@year, @month, 8)
         THEN 1
         WHEN DateCreated >= DATEFROMPARTS(@year, @month, 8) AND DateCreated < DATEFROMPARTS(@year, @month, 15)
         THEN 2
         WHEN DateCreated >= DATEFROMPARTS(@year, @month, 15)) AND DateCreated < DATEFROMPARTS(@year, @month, 22)
         THEN 3
         WHEN DateCreated >= DATEFROMPARTS(@year, @month, 22)) AND DateCreated < DATEFROMPARTS(@year, @month, 29)
         THEN 4
         ELSE 5
    END
)) v(WeekNumber)

WHERE DateCreated >= DATEFROMPARTS(@year, @month, 1)
  AND DateCreated < DATEADD(month, 1, DATEFROMPARTS(@year, @month, 1))
GROUP BY
  v.WeekNumber;

请注意 >= AND < 用于半开区间。这确保包括最后一天的整个时间。

这可以简化 - 您需要做的是 select 总是 5 周,然后外连接到 table 计算每一行的周数。然后就是简单统计每周的行数

注意:这假设第 1 周始终是第 1 周到第 7 周,这不是我们通常计算一个月中的周数的方式。 iso_week 从每月第一个星期四之前的星期一开始 - 美国第 1 周包含每月的第一天到第一个星期六,其余周从星期日开始。

 --==== Some sample data
Declare @testData table (OrderId int, DateCreated datetime2);
 Insert Into @testData (OrderId, DateCreated)
 Values (1, '2021-12-04 06:01:14.6333333')
      , (2, '2021-12-04 07:01:14.6333333')
      , (3, '2021-12-24 00:00:00.0000000')
      , (4, '2021-12-31 06:01:14.6333333')
      , (5, '2021-12-31 06:01:14.6333333')
      , (1, '2021-11-04 06:01:14.6333333')
      , (2, '2021-11-09 07:01:14.6333333')
      , (3, '2021-11-18 00:00:00.0000000')
      , (4, '2021-11-28 06:01:14.6333333')
      , (5, '2021-11-30 06:01:14.6333333');

使用上面的示例数据 - 我们可以这样做:

 --==== Input parameters to select this year and this month
Declare @this_year int = 2021
      , @this_month int = 12;

 --==== Get the first of this month
Declare @start_month date = datefromparts(@this_year, @this_month, 1);

 --==== Force 5 weeks to be selected always (w.week_no) - left join to calculate week DateCreated falls into, join - group and count
 Select w.week_no
      , orders_count = count(t.week_no)
   From (Values (1), (2), (3), (4), (5))        As w(week_no)

   Left Join (Select week_no = datediff(day, @start_month, td.DateCreated) / 7 + 1
                From @testData                  As td
               Where td.DateCreated >= @start_month
                 And td.DateCreated <  dateadd(month, 1, @start_month)
              )                                 As t On t.week_no = w.week_no
  Group By
        w.week_no;

或者 - 我们可以使用 CTE

 --==== Alternate method using CTE instead of derived table
   With order_weeks
     As (
 Select week_no = datediff(day, @start_month, td.DateCreated) / 7 + 1  
   From @testData                               As td
  Where td.DateCreated >= @start_month
    And td.DateCreated <  dateadd(month, 1, @start_month)
        )
 Select w.week_no
      , orders_count = count(ow.week_no)
   From (Values (1), (2), (3), (4), (5))        As w(week_no)

   Left Join order_weeks                        As ow On ow.week_no = w.week_no
  Group By
        w.week_no;

这假定您只会select为特定year/month 组合收集数据。如果这需要能够 select 范围 years/months - 那么您需要为所有可能的范围添加年和月,并计算每个创建日期的 year/month/周。