使用日期回溯选择平均产品费率

时间:2019-04-18 11:28:26

标签: sql-server backtracking

我要查询所有产品的月平均价格,即该月所有星期五的平均价格。我的表和数据脚本是:

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

所以我制作了一个函数,该函数采用月和年并返回所有星期五

 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

四舍五入后

请帮助我解决此问题。 谢谢。

2 个答案:

答案 0 :(得分:1)

使用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

答案 1 :(得分:1)

这些可能是达到新要求的最小更改:

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