SQL Server Dynamic average calculation

mpgws1up  于 2022-12-10  发布在  其他
关注(0)|答案(3)|浏览(147)

I want to add an average cost column which calculates the average across different time periods.
So in the example below, there are 6 months of cost, the first column finds the average across all 6 i.e. average(1,5,8,12,15,20)
The next "Half Period" column determines how many total periods there are and calculates the average across the most recent 3 periods i.e. average(12,15,20)
The first average is straightforward e.g.

AVG(COST)

What I've tried for the half period is:

AVG(COST) OVER (ORDER BY PERIOD ROWS BETWEEN x PRECEDING AND CURRENT ROW)

The x is of course an integer value, how would I write the statement to automatically enter the integer required? i.e. in this example 6 periods requires 3 rows averaged, therefore x=2.
x can be found by some sub-query e.g.

SELECT ( CEILING(COUNT(PERIOD) / 2) - 1) FROM TABLE

Example table:
| Period | Cost |
| ------------ | ------------ |
| Jan | 1 |
| Feb | 5 |
| Mar | 8 |
| Apr | 12 |
| May | 15 |
| Jun | 20 |
Desired Output:
| Period | Cost | All Time Average Cost | Half Period Average Cost |
| ------------ | ------------ | ------------ | ------------ |
| Jan | 1 | 10.1 | 1 |
| Feb | 5 | 10.1 | 3 |
| Mar | 8 | 10.1 | 4.7 |
| Apr | 12 | 10.1 | 8.3 |
| May | 15 | 10.1 | 11.7 |
| Jun | 20 | 10.1 | 15.7 |

rvpgvaaj

rvpgvaaj1#

The main problem here is that you cannot use a variable or an expression for the number of rows Preceeding in the window expression, we must use a literal value for x in the following:
BETWEEN x PRECEDING
If there is a finite number of periods, then we can use a CASE statement to switch between the possible expressions:

CASE
    WHEN CEILING(COUNT(PERIOD) / 2) - 1 <= 1 
        THEN AVG(COST) OVER (ORDER BY PERIOD ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
    WHEN CEILING(COUNT(PERIOD) / 2) - 1 <= 2 
        THEN AVG(COST) OVER (ORDER BY PERIOD ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
    WHEN CEILING(COUNT(PERIOD) / 2) - 1 <= 3 
        THEN AVG(COST) OVER (ORDER BY PERIOD ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
    WHEN CEILING(COUNT(PERIOD) / 2) - 1 <= 4 
        THEN AVG(COST) OVER (ORDER BY PERIOD ROWS BETWEEN 4 PRECEDING AND CURRENT ROW)
    WHEN CEILING(COUNT(PERIOD) / 2) - 1 <= 5 
        THEN AVG(COST) OVER (ORDER BY PERIOD ROWS BETWEEN 5 PRECEDING AND CURRENT ROW)
    WHEN CEILING(COUNT(PERIOD) / 2) - 1 <= 6 
        THEN AVG(COST) OVER (ORDER BY PERIOD ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
END as [Half Period Average Cost]
s4n0splo

s4n0splo2#

我在SQL中添加了这个步骤。但是我的窗口函数拒绝接受变量half_period_rounded。所以我们还没有完全做到这一点。:-)

SQL query

euoag5mw

euoag5mw3#

This looks like a job for sneaky windowed function aggregates!

DECLARE @TABLE TABLE (SaleID INT IDENTITY, Cost DECIMAL(12,4), SaleDateTime DATETIME)
INSERT INTO @TABLE (SaleDateTime, Cost) VALUES
('2022-Jan-01', 1 ),
('2022-Feb-01', 5 ),
('2022-Mar-01', 8 ),
('2022-Apr-01', 12),
('2022-May-01', 15),
('2022-Jun-01', 20)

SELECT DISTINCT DATEPART(YEAR,SaleDateTime) AS Year, DATEPART(MONTH,SaleDateTime) AS MonthNumber, DATENAME(MONTH,SaleDateTime) AS Month,
       AVG(Cost) OVER (ORDER BY (SELECT 1)) AS AllTimeAverage,
       AVG(Cost) OVER (PARTITION BY DATEPART(YEAR,SaleDateTime), DATEPART(MONTH, SaleDateTime) ORDER BY SaleDateTime) AS MonthlyAverage, 
       AVG(Cost) OVER (PARTITION BY DATEPART(YEAR,SaleDateTime), DATEPART(QUARTER,SaleDateTime) ORDER BY SaleDateTime) AS QuarterlyAverage,
       AVG(Cost) OVER (PARTITION BY CASE WHEN SaleDateTime BETWEEN CAST(DATEADD(MONTH,-1,DATEADD(DAY,1-DATEPART(DAY,SaleDateTime),SaleDateTime)) AS DATE) 
                                          AND CAST(DATEADD(MONTH,2,DATEADD(DAY,1-DATEPART(DAY,SaleDateTime),SaleDateTime)) AS DATE) 
                                         THEN 1 END ORDER BY SaleDateTime) AS RollingThreeMonthAverage
 FROM @TABLE
 ORDER BY DATEPART(YEAR,SaleDateTime), DATEPART(MONTH,SaleDateTime)

We're cheating here, and having the case expression find the rows we want in our rolling 3 month window. I've opted to keep it to a rolling window of last month, this month and next month (from the first day of last month, to the last day of next month - '2022-01-01 00:00:00' to '2022-04-01 00:00:00' for February).
Partitioning over the whole result set, month and quarter is straightforward, but the rolling three months isn't much more complicated when you turn it into a case expression describing it.

Year    MonthNumber Month       AllTimeAverage  MonthlyAverage  QuarterlyAverage    RollingThreeMonthAverage
--------------------------------------------------------------------------------------------------------
2022    1           January     10.166666       1.000000        1.000000            1.000000
2022    2           February    10.166666       5.000000        3.000000            3.000000
2022    3           March       10.166666       8.000000        4.666666            4.666666
2022    4           April       10.166666       12.000000       12.000000           6.500000
2022    5           May         10.166666       15.000000       13.500000           8.200000
2022    6           June        10.166666       20.000000       15.666666           10.166666

相关问题