Calculating a Moving Average MySQL?
Use something like
SELECT
sum(close) as sum,
avg(close) as average
FROM (
SELECT
(close)
FROM
tbl
WHERE
date <= '2002-07-05'
AND name_id = 2
ORDER BY
date DESC
LIMIT 9 ) temp
The inner query returns all filtered rows in desc
order, and then you avg
, sum
up those rows returned.
The reason why the query
given by you doesn't work is due to the fact that the sum
is calculated first and the LIMIT
clause is applied after the sum
has already been calculated, giving you the sum
of all the rows present
MySQL calculate moving average of N rows
plan
- self join history on last 50 days
- take average grouping by date and security id ( of current )
query
select curr.date, curr.security_id, avg(prev.close)
from history curr
inner join history prev
on prev.`date` between date_sub(curr.`date`, interval 49 day) and curr.`date`
and curr.security_id = prev.security_id
group by 1, 2
order by 2, 1
;
output
+---------------------------+-------------+--------------------+
| date | security_id | avg(prev.close) |
+---------------------------+-------------+--------------------+
| January, 04 2016 00:00:00 | 1 | 10.770000457763672 |
| January, 05 2016 00:00:00 | 1 | 10.800000190734863 |
| January, 06 2016 00:00:00 | 1 | 10.673333485921225 |
| January, 07 2016 00:00:00 | 1 | 10.59250020980835 |
| January, 08 2016 00:00:00 | 1 | 10.432000160217285 |
| January, 11 2016 00:00:00 | 1 | 10.40166680018107 |
| January, 12 2016 00:00:00 | 1 | 10.344285828726631 |
| January, 13 2016 00:00:00 | 1 | 10.297500133514404 |
| January, 14 2016 00:00:00 | 1 | 10.2877779006958 |
| January, 04 2016 00:00:00 | 2 | 56.15999984741211 |
| January, 05 2016 00:00:00 | 2 | 56.18499946594238 |
| .. | .. | .. |
+---------------------------+-------------+--------------------+
sqlfiddle
reference
- sql rolling averages
modified to use last 50 rows
select
rnk_curr.`date`, rnk_curr.security_id, avg(rnk_prev50.close)
from
(
select `date`, security_id,
@row_num := if(@lag = security_id, @row_num + 1,
if(@lag := security_id, 1, 1)) as row_num
from history
cross join ( select @row_num := 1, @lag := null ) params
order by security_id, `date`
) rnk_curr
inner join
(
select date, security_id, close,
@row_num := if(@lag = security_id, @row_num + 1,
if(@lag := security_id, 1, 1)) as row_num
from history
cross join ( select @row_num := 1, @lag := null ) params
order by security_id, `date`
) rnk_prev50
on rnk_curr.security_id = rnk_prev50.security_id
and rnk_prev50.row_num between rnk_curr.row_num - 49 and rnk_curr.row_num
group by 1,2
order by 2,1
;
sqlfiddle
note
the if function is to force the correct order of evaluation of variables.
Calculating moving average for different values in a column MySQL
You're missing the PARTITION BY
clause:
SELECT team,
date,
AVG(score) OVER (
PARTITION BY team
ORDER BY date ASC ROWS 3 PRECEDING
) AS MA3
FROM table
Note that there will always be an average calculation, regardless of the window size. If you want the average to be null
if your window size is smaller than 3, you could do it like this:
SELECT team,
date,
CASE
WHEN count(*) OVER w <= 3 THEN null
ELSE AVG(score) OVER w
END AS MA3
FROM table
WINDOW w AS (PARTITION BY team ORDER BY date ASC ROWS 3 PRECEDING)
dbfiddle
Side note
Your next question might be about logical windowing, because often, you don't actually want to calculate the average over 3 rows, but over some interval,
like e.g. 3 days. Luckily, MySQL implements this. You could then write:
WINDOW w AS (PARTITION BY team ORDER BY date ASC RANGE INTERVAL 3 DAY PRECEDING)
Moving average using window function in MySQL
You can use Window Functions with Frames:
SELECT
Date,
Price,
CASE WHEN
ROW_NUMBER() OVER (ORDER BY DATE) >= 3 THEN
AVG(Price) OVER (ORDER BY Date
ROWS BETWEEN 2 PRECEDING AND
CURRENT ROW)
ELSE NULL
END AS avg
FROM yourTable
ORDER BY Date;
DB Fiddle Demo
Details:
2 PRECEDING
means two rows above the current row (excluding the current row). We explicitly define Ascending order onDate
. So that would means two closest dates, lower than the current row's dateCURRENT ROW
means the current row.BETWEEN
allows us to consider the rows in the defined range (including boundary conditions).- Since, you want moving average to be
null
for the first two rows, we can check for this usingRow_number()
function inCase .. When
How do I calculate a moving average using MySQL?
This is just off the top of my head, and I'm on the way out the door, so it's untested. I also can't imagine that it would perform very well on any kind of large data set. I did confirm that it at least runs without an error though. :)
SELECT
value_column1,
(
SELECT
AVG(value_column1) AS moving_average
FROM
Table1 T2
WHERE
(
SELECT
COUNT(*)
FROM
Table1 T3
WHERE
date_column1 BETWEEN T2.date_column1 AND T1.date_column1
) BETWEEN 1 AND 20
)
FROM
Table1 T1
Getting moving average with window function
You may try this version:
WITH cte AS (
SELECT visited_on, SUM(amount) AS amount
FROM Customer
GROUP BY visited_on
)
SELECT
visited_on,
SUM(amount) OVER (ORDER BY visited_on
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS amount,
ROUND(AVG(amount) OVER (ORDER BY visited_on
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW), 2) AS average_amount
FROM cte
ORDER BY
visited_on;
The logic here is to first aggregate the customers table by the visited date, to get the sum of the amounts per day. Then, we use your current logic to get the rolling 7 day sums and averages using analytic functions. The CTE I have used above is critical, without which you might not be taking the previous 7 days. For example, if a given day had 7 customers active, then your current logic would only take a single 7 for the rolling sum/average, not an actual 7 day window.
How to calculate a moving average in MYSQL
That is what I call a good challenge. My solution first creates a counter for the values and uses it as a table. From it I select everything and join with the same query as a subquery checking the position of the counter on both. Once the query works it just need an inner join with the actual table to do the update. Here it is my solution:
update stock_history tb1
inner join
(
select a.id,
case when a.step < 3 then null
else
(select avg(b.close)
from (
select hh.*,
@stp:=@stp+1 stp
from stock_history hh,
(select @sum:=0, @stp:=0) x
order by hh.dt
limit 17823232
) b
where b.stp >= a.step-2 and b.stp <= a.step
)
end dmal_3
from (select h1.*,
@step:=@step+1 step
from stock_history h1,
(select @sum:=0, @step:=0) x
order by h1.dt
limit 17823232
) a
) x on tb1.id = x.id
set tb1.dmal_3 = x.dmal_3;
I changed some columns names for easiness of my test. Here it is the working SQLFiddle: http://sqlfiddle.com/#!9/e7dc00/1
If you have any doubt, let me know so I can clarify!
Edit
The limit 17823232
clause was added there in the subqueries because I don't know which version of MySql you are in. Depending on it (>= 5.7, not sure exactly) the database optimizer will ignore the internal order by
clauses making it not work the way it should. I just chose a random big number usually you can use the maximum allowed.
The only column with different colunm name between your table and mine is the date
one which I named dt
because date
is a reserved word and you should use backticks ( ` ) to use such columns, therefore I will left it as dt
in above query.
Related Topics
SQL Get the Last Date Time Record
Possible to Restore a Backup of SQL Server 2014 on SQL Server 2012
Meaning of "Select Tables Optimized Away" in MySQL Explain Plan
Calculate Time Difference (Only Working Hours) in Minutes Between Two Dates
Update Table Based on Another Table
Set Constraints All Deferred Not Working as Expected
SQL - Find Missing Int Values in Mostly Ordered Sequential Series
Merging Intervals in One Pass in SQL
SQL Comma Delimted Column => to Rows Then Sum Totals
Convert Timestamp to Date in Oracle SQL
Is the 'As' Keyword Required in Oracle to Define an Alias
How to Get Max(Date) from Given Set of Data Grouped by Some Fields Using Pyspark
Does Oracle Roll Back the Transaction on an Error
Group by Two Columns and Display Grand Total in Every Row