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 点之后的任何订单。
根据我迄今为止所做的研究,我认为我应该使用 DATEADD
、DATEDIFF
和 COUNT
的某种组合(而不是 SUM
,这样我就可以做 GROUP BY
) 我很了解这些 functions/statements 如何独立工作,但无法将它们组合在一起以实现我的目标。任何和所有帮助将不胜感激!
我假设:
- 您希望第 1 周从每月的第一天开始计算并且始终是前 7 天,并且
- 第 5 周应始终是部分周(即 28 日之后的一个月的末尾),除非在非闰年的 2 月不存在。
- 您想在结果集中看到零周。
如果所有这些假设都是正确的,我会使用这样的东西:
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/周。
我的任务是返回指定年份和月份内的订单数量 '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 点之后的任何订单。
根据我迄今为止所做的研究,我认为我应该使用 DATEADD
、DATEDIFF
和 COUNT
的某种组合(而不是 SUM
,这样我就可以做 GROUP BY
) 我很了解这些 functions/statements 如何独立工作,但无法将它们组合在一起以实现我的目标。任何和所有帮助将不胜感激!
我假设:
- 您希望第 1 周从每月的第一天开始计算并且始终是前 7 天,并且
- 第 5 周应始终是部分周(即 28 日之后的一个月的末尾),除非在非闰年的 2 月不存在。
- 您想在结果集中看到零周。
如果所有这些假设都是正确的,我会使用这样的东西:
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/周。