How to do a moving average on a range of dates in SQL?

Let’s just say I have two columns, DATE and VALUE. The dates are not necessarily regularly spaced intervals. How can I create a moving average on VALUE over the past N days? I’m using postgres but mysql solutions would also be helpful.

Data:

DATE    VALUE
2012-11-05 10
2012-10-29 31
2012-10-22 108
2012-10-17 3654
2012-10-16 1187
2012-10-15 12033
2012-10-09 41
2012-10-01 85
2012-09-25 20
2012-09-24 285
2012-09-17 20
2012-09-10 20
2012-09-04 41
2012-08-27 63
2012-08-20 52
2012-08-13 160

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

The MySQL example below covers a sliding 7-day window:

select t1.`DATE`, AVG(t2.`VALUE`) as MV_AVG
from MyTable t1
left outer join MyTable t2 
    on t2.`DATE` between DATE_ADD(t1.`DATE`, INTERVAL -6 DAY) 
        and t1.`DATE`
group by t1.`DATE`

SQL Fiddle Example

Output:

|                             DATE |    MV_AVG |
------------------------------------------------
|    August, 12 2012 20:00:00+0000 |       160 |
|    August, 19 2012 20:00:00+0000 |        52 |
|    August, 26 2012 20:00:00+0000 |        63 |
| September, 03 2012 20:00:00+0000 |        41 |
| September, 09 2012 20:00:00+0000 |      30.5 |
| September, 16 2012 20:00:00+0000 |        20 |
| September, 23 2012 20:00:00+0000 |       285 |
| September, 24 2012 20:00:00+0000 |     152.5 |
| September, 30 2012 20:00:00+0000 |      52.5 |
|   October, 08 2012 20:00:00+0000 |        41 |
|   October, 14 2012 20:00:00+0000 |      6037 |
|   October, 15 2012 20:00:00+0000 |      6610 |
|   October, 16 2012 20:00:00+0000 | 5624.6667 |
|   October, 21 2012 20:00:00+0000 | 1649.6667 |
|   October, 28 2012 20:00:00+0000 |        31 |
|  November, 04 2012 19:00:00+0000 |        10 |

Method 2

RedFilter’s excellent answer uses non-evenly spaced buckets. If you want evenly spaced buckets do something like this:

SELECT
DATE_FORMAT(CURDATE() - INTERVAL ((i.id - 0) * 7) - 1 DAY, '%Y-%m-%d') AS start,
DATE_FORMAT(CURDATE() - INTERVAL ((i.id - 1) * 7) DAY, '%Y-%m-%d') AS end,
AVG(d.value) average
FROM
ids i
LEFT OUTER JOIN
dates d ON
d.date BETWEEN 
  (CURDATE() - INTERVAL ((i.id - 0) * 7) - 1 DAY) AND
  (CURDATE() - INTERVAL ((i.id - 1) * 7) DAY)
WHERE
i.id BETWEEN 0 AND 20
GROUP BY
1
ORDER BY
1 DESC;

which provides this:

+------------+------------+-----------+
| start      | end        | average   |
+------------+------------+-----------+
| 2012-11-01 | 2012-11-07 |   10.0000 |
| 2012-10-25 | 2012-10-31 |   31.0000 |
| 2012-10-18 | 2012-10-24 |  108.0000 |
| 2012-10-11 | 2012-10-17 | 5624.6667 |
| 2012-10-04 | 2012-10-10 |   41.0000 |
| 2012-09-27 | 2012-10-03 |   85.0000 |
| 2012-09-20 | 2012-09-26 |  152.5000 |
| 2012-09-13 | 2012-09-19 |   20.0000 |
| 2012-09-06 | 2012-09-12 |   20.0000 |
| 2012-08-30 | 2012-09-05 |   41.0000 |
| 2012-08-23 | 2012-08-29 |   63.0000 |
| 2012-08-16 | 2012-08-22 |   52.0000 |
| 2012-08-09 | 2012-08-15 |  160.0000 |
| 2012-08-02 | 2012-08-08 |      NULL |
| 2012-07-26 | 2012-08-01 |      NULL |
| 2012-07-19 | 2012-07-25 |      NULL |
| 2012-07-12 | 2012-07-18 |      NULL |
| 2012-07-05 | 2012-07-11 |      NULL |
| 2012-06-28 | 2012-07-04 |      NULL |
| 2012-06-21 | 2012-06-27 |      NULL |
+------------+------------+-----------+
20 rows in set (0.00 sec)

See http://sqlfiddle.com/#!2/78c52/39 for a working example without CURDATE().


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x