计算移动平均线 MySQL?
Posted
技术标签:
【中文标题】计算移动平均线 MySQL?【英文标题】:Calculating a Moving Average MySQL? 【发布时间】:2013-04-20 13:43:44 【问题描述】:美好的一天,
我正在使用以下代码来计算 9 天移动平均线。
SELECT SUM(close)
FROM tbl
WHERE date <= '2002-07-05'
AND name_id = 2
ORDER BY date DESC
LIMIT 9
但它不起作用,因为它在调用限制之前首先计算所有返回的字段。换句话说,它将计算该日期之前或等于该日期的所有收盘价,而不仅仅是最后 9 个收盘价。
所以我需要从返回的选择中计算 SUM,而不是直接计算。
IE。从 SELECT 中选择 SUM...
现在我将如何去做?它是否非常昂贵或有更好的方法?
【问题讨论】:
这个平均“移动”如何? 现在只是静态平均计算。动人之处在于它每天都在计算。 【参考方案1】:如果你想要每个日期的移动平均线,那么试试这个:
SELECT date, SUM(close),
(select avg(close) from tbl t2 where t2.name_id = t.name_id and datediff(t2.date, t.date) <= 9
) as mvgAvg
FROM tbl t
WHERE date <= '2002-07-05' and
name_id = 2
GROUP BY date
ORDER BY date DESC
它使用相关子查询来计算 9 个值的平均值。
【讨论】:
更好。我在每日触发器上使用它,所以我只需要特定日期的 MA,因为我将 9 天移动平均线保存在数据库中只是为了速度。但有了这个,我可以让用户为移动平均线设置自己的周期,缺点是速度。谢谢。 @surfer100 奇怪的是这不是公认的答案 碰到这个旧线程,因为我正在使用纪元时间戳寻找相同的答案。这里的问题是这不包括周末。 @fractal5 您可以将时间戳转换为 DATE。要考虑缺少的日期,您需要加入一个包含所有日期(预先生成)的表。 我认为 datediff() 必须是between 0 and 9
,否则也会计算负 datediff()。【参考方案2】:
从 mysql 8 开始,您应该为此使用窗口函数。使用窗口RANGE
子句,可以在一个区间内创建logical window,功能非常强大。像这样的:
SELECT
date,
close,
AVG (close) OVER (ORDER BY date DESC RANGE INTERVAL 9 DAY PRECEDING)
FROM tbl
WHERE date <= DATE '2002-07-05'
AND name_id = 2
ORDER BY date DESC
例如:
WITH t (date, `close`) AS (
SELECT DATE '2020-01-01', 50 UNION ALL
SELECT DATE '2020-01-03', 54 UNION ALL
SELECT DATE '2020-01-05', 51 UNION ALL
SELECT DATE '2020-01-12', 49 UNION ALL
SELECT DATE '2020-01-13', 59 UNION ALL
SELECT DATE '2020-01-15', 30 UNION ALL
SELECT DATE '2020-01-17', 35 UNION ALL
SELECT DATE '2020-01-18', 39 UNION ALL
SELECT DATE '2020-01-19', 47 UNION ALL
SELECT DATE '2020-01-26', 50
)
SELECT
date,
`close`,
COUNT(*) OVER w AS c,
SUM(`close`) OVER w AS s,
AVG(`close`) OVER w AS a
FROM t
WINDOW w AS (ORDER BY date DESC RANGE INTERVAL 9 DAY PRECEDING)
ORDER BY date DESC
导致:
date |close|c|s |a |
----------|-----|-|---|-------|
2020-01-26| 50|1| 50|50.0000|
2020-01-19| 47|2| 97|48.5000|
2020-01-18| 39|3|136|45.3333|
2020-01-17| 35|4|171|42.7500|
2020-01-15| 30|4|151|37.7500|
2020-01-13| 59|5|210|42.0000|
2020-01-12| 49|6|259|43.1667|
2020-01-05| 51|3|159|53.0000|
2020-01-03| 54|3|154|51.3333|
2020-01-01| 50|3|155|51.6667|
【讨论】:
是的,MySQL 支持窗口函数真是太好了。我喜欢带有移动平均线的窗口链接:db-fiddle.com/f/p9QAcJT7U24xHysqKCHFyQ/0【参考方案3】:使用类似的东西
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
内部查询以desc
的顺序返回所有过滤的行,然后您将avg
、sum
向上返回这些行。
你给出的query
不起作用的原因是因为sum
是先计算出来的,LIMIT
子句是在sum
已经计算出来之后应用的,给你存在的所有行的sum
【讨论】:
。 .这将返回一个值。移动平均值是指“n”条记录的每个日期的单独值。 是的,我假设 OP 想要特定日期的移动平均值,2002-07-05
如果提供了特定的 name_id,此解决方案有效,有没有办法获得表中每个 name_id 的移动平均值?所以意味着输出将是每个 name_id 的移动平均值表
您可以删除name_id
的过滤器并在group by子句中添加此字段,您还需要删除limit
子句并将9天的日期条件范围添加为@ 987654333@【参考方案4】:
另一种技术是做一个表格:
CREATE TABLE `tinyint_asc` (
`value` tinyint(3) unsigned NOT NULL default '0',
PRIMARY KEY (value)
) ;
INSERT INTO `tinyint_asc` VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23),(24),(25),(26),(27),(28),(29),(30),(31),(32),(33),(34),(35),(36),(37),(38),(39),(40),(41),(42),(43),(44),(45),(46),(47),(48),(49),(50),(51),(52),(53),(54),(55),(56),(57),(58),(59),(60),(61),(62),(63),(64),(65),(66),(67),(68),(69),(70),(71),(72),(73),(74),(75),(76),(77),(78),(79),(80),(81),(82),(83),(84),(85),(86),(87),(88),(89),(90),(91),(92),(93),(94),(95),(96),(97),(98),(99),(100),(101),(102),(103),(104),(105),(106),(107),(108),(109),(110),(111),(112),(113),(114),(115),(116),(117),(118),(119),(120),(121),(122),(123),(124),(125),(126),(127),(128),(129),(130),(131),(132),(133),(134),(135),(136),(137),(138),(139),(140),(141),(142),(143),(144),(145),(146),(147),(148),(149),(150),(151),(152),(153),(154),(155),(156),(157),(158),(159),(160),(161),(162),(163),(164),(165),(166),(167),(168),(169),(170),(171),(172),(173),(174),(175),(176),(177),(178),(179),(180),(181),(182),(183),(184),(185),(186),(187),(188),(189),(190),(191),(192),(193),(194),(195),(196),(197),(198),(199),(200),(201),(202),(203),(204),(205),(206),(207),(208),(209),(210),(211),(212),(213),(214),(215),(216),(217),(218),(219),(220),(221),(222),(223),(224),(225),(226),(227),(228),(229),(230),(231),(232),(233),(234),(235),(236),(237),(238),(239),(240),(241),(242),(243),(244),(245),(246),(247),(248),(249),(250),(251),(252),(253),(254),(255);
以后可以这样使用了:
select date_add(tbl.date, interval tinyint_asc.value day) as mydate, count(*), sum(myvalue)
from tbl inner join tinyint_asc.value <= 30 -- for a 30 day moving average
where date(date_add(o.created_at, interval tinyint_asc.value day)) between '2016-01-01' and current_date()
group by mydate
【讨论】:
【参考方案5】:这个查询很快:
select date, name_id,
case @i when name_id then @i:=name_id else (@i:=name_id)
and (@n:=0)
and (@a0:=0) and (@a1:=0) and (@a2:=0) and (@a3:=0) and (@a4:=0) and (@a5:=0) and (@a6:=0) and (@a7:=0) and (@a8:=0)
end as a,
case @n when 9 then @n:=9 else @n:=@n+1 end as n,
@a0:=@a1,@a1:=@a2,@a2:=@a3,@a3:=@a4,@a4:=@a5,@a5:=@a6,@a6:=@a7,@a7:=@a8,@a8:=close,
(@a0+@a1+@a2+@a3+@a4+@a5+@a6+@a7+@a8)/@n as av
from tbl,
(select @i:=0, @n:=0,
@a0:=0, @a1:=0, @a2:=0, @a3:=0, @a4:=0, @a5:=0, @a6:=0, @a7:=0, @a8:=0) a
where name_id=2
order by name_id, date
如果您需要平均超过 50 或 100 个值,编写起来很乏味,但是 值得努力。速度接近有序选择。
【讨论】:
以上是关于计算移动平均线 MySQL?的主要内容,如果未能解决你的问题,请参考以下文章