Select 使用日期回溯的平均产品率
Select average of product rate using date backtracking
我查询的是所有产品的月平均费率,该月所有周五费率的平均值。我的 table 和数据脚本是:
CREATE TABLE [dbo].[Product_Entry](
[ProductCode] [varchar](10) NOT NULL,
[Rate] [decimal](18, 0) NULL,
[RateDate] [date] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050101', CAST(56 AS Decimal(18, 0)), CAST(N'2019-04-05' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050102', CAST(60 AS Decimal(18, 0)), CAST(N'2019-04-05' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050103', CAST(65 AS Decimal(18, 0)), CAST(N'2019-04-04' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050101', CAST(50 AS Decimal(18, 0)), CAST(N'2019-04-12' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050102', CAST(64 AS Decimal(18, 0)), CAST(N'2019-04-11' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050103', CAST(70 AS Decimal(18, 0)), CAST(N'2019-04-12' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050101', CAST(55 AS Decimal(18, 0)), CAST(N'2019-04-15' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050102', CAST(50 AS Decimal(18, 0)), CAST(N'2019-04-16' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050103', CAST(68 AS Decimal(18, 0)), CAST(N'2019-04-17' AS Date))
GO
所以我制作了一个函数,它接受月份和年份以及 returns 所有星期五
CREATE PROCEDURE [dbo].[GetallFridaysinMonth] (
@month VARCHAR(2) = NULL
,@year VARCHAR(5) = NULL
)
AS
BEGIN
SELECT Fridays = DATEADD(yy, DATEDIFF(yy, 0, '' + @year + '-' + @month + '-' + '01'), n.num)
INTO #t
FROM (
SELECT TOP 366 num = ROW_NUMBER() OVER (
ORDER BY a.NAME
) - 1
FROM dbo.syscolumns a
,dbo.syscolumns b
) n
WHERE DATENAME(weekday, DATEADD(yy, DATEDIFF(yy, 0, '' + @year + '-' + @month + '-' + '01'), n.num)) = 'Friday'
SELECT Fridays
FROM #t
WHERE datepart(month, Fridays) = @month
DROP TABLE #t
END
然后用这个函数查询平均值
BEGIN
CREATE TABLE #t121 (Fridays DATETIME,id INT IDENTITY(1, 1));
INSERT INTO #t121 (Fridays)
EXEC dbo.GetallFridaysinMonth @month,@year;
SELECT ProductCode
,convert(DECIMAL(18), avg(Rate)) AS AverageRate
FROM dbo.product_entry
WHERE RateDate IN (
SELECT Fridays
FROM #t121
)
GROUP BY ProductCode
DROP TABLE #t121
END
这非常有效,直到我的客户修改要求并说如果周五没有费率然后在周四检查,如果没有在周四然后在周三检查意味着回溯到周六找到周费率。
现在,如果只有 2 或 3 个产品,那么我可以使用 Case 条件,但不知道如何回溯所有超过 250 个的产品。
我对演示数据的预期结果是
ProductCode AverageRate
-------------------------
050101 54
050102 58
050103 68
四舍五入后
请帮我解决这个问题。
谢谢。
使用 CTE 和 Row_Number() 我按产品代码对您的数据进行了分区,然后按星期几降序排列了每月的第几周。 (我将查询设置为仅检索当前年份)。然后,仅查看第 1 行,它将按每周最后一天的产品代码进行平均。
;With cte1 As
(
Select
*,
Case When DatePart(dw,RateDate) = 7 Then 0 Else DatePart(dw,RateDate) End As dowN, --Day of week Number (Make Saturday = 0 instead of 7)
Datepart(day, datediff(day, 0, RateDate)/7 * 7)/7 + 1 As wom --Week of Month Number
From Product_Entry
Where Year(RateDate) = Year(GetDate()) --Current Year Only
), cte2 As
(
Select
Row_Number() Over (Partition By ProductCode, wom Order By dowN Desc) As rn,
*
From cte1
)
Select ProductCode, Cast(Round(AVG(Rate),0) As Int) As AverageRate From cte2
Where rn = 1
Group By ProductCode
这些可能是实现新要求的最小更改:
begin
create table #t121 (Fridays datetime);
insert into #t121 (Fridays)
exec dbo.GetallFridaysinMonth @month, @year;
with AVGDaily as (
select ProductCode, RateDate, AVG(Rate) as AVGDay
from dbo.Product_Entry
where month(RateDate)=@month and year(RateDate)=@year
group by ProductCode, RateDate
)
select ProductCode
, convert(decimal(18), AVG(AVGRate)) as AverageRate
from (
select distinct t.Fridays, AVGDaily.ProductCode,
AVGRate=(
select top (1) AVGDay
from AVGDaily i2
where ProductCode = AVGDaily.ProductCode
and i2.RateDate between
DATEADD(DD, -6, t.Fridays) and t.Fridays
order by RateDate desc)
from AVGDaily, #t121 as t) g
group by ProductCode
drop table #t121
end
也许过程可以改进:
CREATE Proc GetallFridaysinMonth(@month varchar(2), @year varchar(4), @dw tinyint=5) as
declare @dateStart datetime, @maxDDinMM tinyint
select @dateStart = cast(right('20'+@year,4)+right('0'+@month,2)+'01' as datetime)
, @maxDDinMM = DATEDIFF(DD, @dateStart, DATEADD(MM, 1, @dateStart)) - 1;
with ADD_cte as (
select 0 as AddDays
union all
select AddDays + 1 from ADD_cte where AddDays < @maxDDinMM
)
select DATEADD(DD, AddDays, @dateStart) as SalesDay
from ADD_cte
where (DATEPART(DW, DATEADD(DD, AddDays, @dateStart)) + @@DATEFIRST + 5) % 7 + 1 = @dw
我查询的是所有产品的月平均费率,该月所有周五费率的平均值。我的 table 和数据脚本是:
CREATE TABLE [dbo].[Product_Entry](
[ProductCode] [varchar](10) NOT NULL,
[Rate] [decimal](18, 0) NULL,
[RateDate] [date] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050101', CAST(56 AS Decimal(18, 0)), CAST(N'2019-04-05' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050102', CAST(60 AS Decimal(18, 0)), CAST(N'2019-04-05' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050103', CAST(65 AS Decimal(18, 0)), CAST(N'2019-04-04' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050101', CAST(50 AS Decimal(18, 0)), CAST(N'2019-04-12' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050102', CAST(64 AS Decimal(18, 0)), CAST(N'2019-04-11' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050103', CAST(70 AS Decimal(18, 0)), CAST(N'2019-04-12' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050101', CAST(55 AS Decimal(18, 0)), CAST(N'2019-04-15' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050102', CAST(50 AS Decimal(18, 0)), CAST(N'2019-04-16' AS Date))
GO
INSERT [dbo].[Product_Entry] ([ProductCode], [Rate], [RateDate]) VALUES (N'050103', CAST(68 AS Decimal(18, 0)), CAST(N'2019-04-17' AS Date))
GO
所以我制作了一个函数,它接受月份和年份以及 returns 所有星期五
CREATE PROCEDURE [dbo].[GetallFridaysinMonth] (
@month VARCHAR(2) = NULL
,@year VARCHAR(5) = NULL
)
AS
BEGIN
SELECT Fridays = DATEADD(yy, DATEDIFF(yy, 0, '' + @year + '-' + @month + '-' + '01'), n.num)
INTO #t
FROM (
SELECT TOP 366 num = ROW_NUMBER() OVER (
ORDER BY a.NAME
) - 1
FROM dbo.syscolumns a
,dbo.syscolumns b
) n
WHERE DATENAME(weekday, DATEADD(yy, DATEDIFF(yy, 0, '' + @year + '-' + @month + '-' + '01'), n.num)) = 'Friday'
SELECT Fridays
FROM #t
WHERE datepart(month, Fridays) = @month
DROP TABLE #t
END
然后用这个函数查询平均值
BEGIN
CREATE TABLE #t121 (Fridays DATETIME,id INT IDENTITY(1, 1));
INSERT INTO #t121 (Fridays)
EXEC dbo.GetallFridaysinMonth @month,@year;
SELECT ProductCode
,convert(DECIMAL(18), avg(Rate)) AS AverageRate
FROM dbo.product_entry
WHERE RateDate IN (
SELECT Fridays
FROM #t121
)
GROUP BY ProductCode
DROP TABLE #t121
END
这非常有效,直到我的客户修改要求并说如果周五没有费率然后在周四检查,如果没有在周四然后在周三检查意味着回溯到周六找到周费率。
现在,如果只有 2 或 3 个产品,那么我可以使用 Case 条件,但不知道如何回溯所有超过 250 个的产品。
我对演示数据的预期结果是
ProductCode AverageRate
-------------------------
050101 54
050102 58
050103 68
四舍五入后
请帮我解决这个问题。 谢谢。
使用 CTE 和 Row_Number() 我按产品代码对您的数据进行了分区,然后按星期几降序排列了每月的第几周。 (我将查询设置为仅检索当前年份)。然后,仅查看第 1 行,它将按每周最后一天的产品代码进行平均。
;With cte1 As
(
Select
*,
Case When DatePart(dw,RateDate) = 7 Then 0 Else DatePart(dw,RateDate) End As dowN, --Day of week Number (Make Saturday = 0 instead of 7)
Datepart(day, datediff(day, 0, RateDate)/7 * 7)/7 + 1 As wom --Week of Month Number
From Product_Entry
Where Year(RateDate) = Year(GetDate()) --Current Year Only
), cte2 As
(
Select
Row_Number() Over (Partition By ProductCode, wom Order By dowN Desc) As rn,
*
From cte1
)
Select ProductCode, Cast(Round(AVG(Rate),0) As Int) As AverageRate From cte2
Where rn = 1
Group By ProductCode
这些可能是实现新要求的最小更改:
begin
create table #t121 (Fridays datetime);
insert into #t121 (Fridays)
exec dbo.GetallFridaysinMonth @month, @year;
with AVGDaily as (
select ProductCode, RateDate, AVG(Rate) as AVGDay
from dbo.Product_Entry
where month(RateDate)=@month and year(RateDate)=@year
group by ProductCode, RateDate
)
select ProductCode
, convert(decimal(18), AVG(AVGRate)) as AverageRate
from (
select distinct t.Fridays, AVGDaily.ProductCode,
AVGRate=(
select top (1) AVGDay
from AVGDaily i2
where ProductCode = AVGDaily.ProductCode
and i2.RateDate between
DATEADD(DD, -6, t.Fridays) and t.Fridays
order by RateDate desc)
from AVGDaily, #t121 as t) g
group by ProductCode
drop table #t121
end
也许过程可以改进:
CREATE Proc GetallFridaysinMonth(@month varchar(2), @year varchar(4), @dw tinyint=5) as
declare @dateStart datetime, @maxDDinMM tinyint
select @dateStart = cast(right('20'+@year,4)+right('0'+@month,2)+'01' as datetime)
, @maxDDinMM = DATEDIFF(DD, @dateStart, DATEADD(MM, 1, @dateStart)) - 1;
with ADD_cte as (
select 0 as AddDays
union all
select AddDays + 1 from ADD_cte where AddDays < @maxDDinMM
)
select DATEADD(DD, AddDays, @dateStart) as SalesDay
from ADD_cte
where (DATEPART(DW, DATEADD(DD, AddDays, @dateStart)) + @@DATEFIRST + 5) % 7 + 1 = @dw