0

I have a table in my Postgresql DB that has the fields: product_id, date, sales_amount. I am calculating a simple moving average for the last 1 week using the below SQL

SELECT date,  
       AVG(amount)
       OVER(PARTITION BY product_id ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_amount
FROM sales 

How can I calculate a smoothed moving average(smma) instead the simple moving average above? I have found that the formula is smma_today = smma_yesterday * (lookback_period - 1) + amount) / lookback_period
but how to translate to SQL? A CTE or function or query approach suggestion will be appreciated

1
  • Can you provide sample data and desired results? What is "lookback_period"? Commented Dec 6, 2020 at 18:15

2 Answers 2

1

I am pretty sure you will need recursion since your formula depends on using a value calculated for a previous row in the current row.

with recursive mma as (
  (select distinct on (product_id) *, ddate as basedate, 
          amount as sm_mov_avg
     from smma
    order by product_id, ddate)
  union all
  select smma.*, mma.basedate,
         (  mma.sm_mov_avg
          * least(smma.ddate - mma.basedate, 6)
          + smma.amount) / least(smma.ddate - mma.basedate + 1, 7)
    from mma
         join smma on smma.product_id = mma.product_id
          and smma.ddate = mma.ddate + 1
)
select ddate, product_id, amount, round(sm_mov_avg, 2) as sm_mov_avg,
       round(
         avg(amount) over (partition by product_id
                               order by ddate
                        rows between 6 preceding
                                 and current row), 2) as mov_avg
  from mma;

Please note how the smooth moving average and the moving average begin to diverge after you reach the lookback of seven days:

 ddate      | product_id | amount | sm_mov_avg | mov_avg
 :--------- | ---------: | -----: | ---------: | ------:
 2020-11-01 |          1 |      8 |       8.00 |    8.00
 2020-11-02 |          1 |      4 |       6.00 |    6.00
 2020-11-03 |          1 |      7 |       6.33 |    6.33
 2020-11-04 |          1 |      9 |       7.00 |    7.00
 2020-11-05 |          1 |      4 |       6.40 |    6.40
 2020-11-06 |          1 |      6 |       6.33 |    6.33
 2020-11-07 |          1 |      4 |       6.00 |    6.00
 2020-11-08 |          1 |      1 |       5.29 |    5.00
 2020-11-09 |          1 |      8 |       5.67 |    5.57
 2020-11-10 |          1 |     10 |       6.29 |    6.00
 2020-11-11 |          1 |      8 |       6.54 |    5.86
 2020-11-12 |          1 |      4 |       6.17 |    5.86
 2020-11-13 |          1 |      3 |       5.72 |    5.43
 2020-11-14 |          1 |      2 |       5.19 |    5.14
 2020-11-15 |          1 |      5 |       5.16 |    5.71
 2020-11-16 |          1 |      8 |       5.57 |    5.71
 2020-11-17 |          1 |      4 |       5.34 |    4.86
 2020-11-18 |          1 |     10 |       6.01 |    5.14
 2020-11-19 |          1 |      5 |       5.86 |    5.29
 2020-11-20 |          1 |      3 |       5.46 |    5.29
 2020-11-21 |          1 |      3 |       5.10 |    5.43
 2020-11-22 |          1 |      9 |       5.66 |    6.00
 2020-11-23 |          1 |      7 |       5.85 |    5.86
 2020-11-24 |          1 |      1 |       5.16 |    5.43
 2020-11-25 |          1 |     10 |       5.85 |    5.43
 2020-11-26 |          1 |      7 |       6.01 |    5.71
 2020-11-27 |          1 |      8 |       6.30 |    6.43
 2020-11-28 |          1 |      8 |       6.54 |    7.14
 2020-11-29 |          1 |      1 |       5.75 |    6.00
 2020-11-30 |          1 |      9 |       6.21 |    6.29

Working Fiddle

Sign up to request clarification or add additional context in comments.

Comments

0

Many thanks for Mike Organek's answer that showed the way forward for the calculation was a recursive approach on the query. I am starting off with the simple moving average at some distant point in the past and thereafter using the smoothed average daily which has given us exactly what we needed

with recursive my_table_with_rn as 
(
SELECT 
    product_id,
    amount,
    sale_date,
    row_number() over (partition by product_id order by sale_date) as rn
FROM sale
where 1=1
    and sale_date > '01-Jan-2018'
    order by sale_date
),
rec_query(rn, product_id, amount, sale_date, smma) as 
(
    SELECT 
        rn,
        product_id,
        amount,
        sale_date,
        AVG(amount)  OVER(PARTITION BY product_id ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS smma --first entry is a simple moving average to start off
        from my_table_with_rn 
        where rn = 1
    union all
    select 
        t.rn, t.product_id, t.amount, t.sale_date, 
        (p.smma * (7 - 1) + amount) / 7 -- 7 is the lookback_period; formula is smma_today = smma_previous * (lookback_period - 1) + amount) / lookback_period
    from rec_query p
    join my_table_with_rn t on 
    (t.rn = p.rn + 1 AND t.product_id = p.product_id)
)   
SELECT * FROM rec_query

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.