Given a table of dates and daily values ...
DROP TABLE IF EXISTS t; CREATE TABLE t (dt DATE, qty INT); INSERT INTO t VALUES ('2025-1-1',5),('2025-1-2',6),('2025-1-3',7),('2025-1-4',8),('2025-1-5',9), ('2025-1-6',10),('2025-1-7',11),('2025-1-8',12),('2025-1-9',13);... we want the moving 5-day average of qty . A brute force logic for it would be...
SELECT a.dt, a.qty, Round( ( SELECT SUM(b.qty) / COUNT(b.qty) FROM t AS b WHERE DATEDIFF(a.dt, b.dt) BETWEEN 0 AND 4 ), 2 ) AS '5dayMovingAvg' FROM t AS a ORDER BY a.dt; +------------+------+---------------+ | dt | qty | 5dayMovingAvg | +------------+------+---------------+ | 2025-01-01 | 5 | 5.00 | | 2025-01-02 | 6 | 5.50 | | 2025-01-03 | 7 | 6.00 | | 2025-01-04 | 8 | 6.50 | | 2025-01-05 | 9 | 7.00 | | 2025-01-06 | 10 | 8.00 | | 2025-01-07 | 11 | 9.00 | | 2025-01-08 | 12 | 10.00 | | 2025-01-09 | 13 | 11.00 | +------------+------+---------------+... but correlated subqueries can be slow, and we can get rid of this one: SELECT a.dt, format(sum(b.qty)/count(b.qty),2) 5dayMovAvg FROM t a JOIN t b on datediff(a.dt,b.dt) BETWEEN 0 AND 4 GROUP BY a.dt;Since MySQL 8.0 and MariaDB 10,2, windowing functions simplify moving averages enormously ... SELECT dt, AVG(qty) OVER( ORDER BY dt ASC ROWS 5 PRECEDING ) AS mavg5 FROM t;Obviously the pattern generalises to as many moving averages and as many columns as you want. Adjust the averaging period by specifying PRECEDING and FOLLOWING . For more info on windowing see "Windowing: introduction" on the Common Queries page.Last updated 7 Apr 2024 |