如何使用 Postgres 计算平滑移动平均线 SQL

How to calculate smoothed moving average using Postgres SQL

我的 Postgresql 数据库中有一个 table,它具有以下字段:product_id, date, sales_amount。 我正在使用以下 SQL

计算过去 1 周的简单移动平均线
SELECT date,  
       AVG(amount)
       OVER(PARTITION BY product_id ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_amount
FROM sales 

如何计算平滑移动平均线 (smma) 而不是上面的简单移动平均线?我发现公式是 smma_today = smma_yesterday * (lookback_period - 1) + amount) / lookback_period
但如何翻译成 SQL? CTE 或功能或查询方法建议将不胜感激

我很确定您需要递归,因为您的公式取决于使用为当前行中的前一行计算的值。

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;

请注意平滑移动平均线和移动平均线在您到达 7 天回溯后如何开始背离:

 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

非常感谢 ,它显示了计算的前进方向是查询的递归方法。我从过去某个遥远点的简单移动平均线开始,然后每天使用平滑的平均线,这正是我们所需要的

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