如何在相关子查询中计算MySQL中的移动平均值?

时间:2012-04-12 10:36:12

标签: mysql correlated-subquery moving-average

我想创建一个时间线报告,为时间线中的每个日期显示数据集中最新N个数据点的移动平均值,该数据集包含一些度量和测量日期。我每天都有一张日历表来提供日期。我可以计算一个时间线来显示该日期之前的整体平均值,相当简单地使用相关子查询(实际情况比这复杂得多,但它基本上可以简化为此):

SELECT  c.date
,       (   SELECT  AVERAGE(m.value) 
            FROM    measures as m
            WHERE   m.measured_on_dt <= c.date
        ) as `average_to_date`
FROM    calendar c
WHERE   c.date between date1 AND date2  -- graph boundaries
ORDER BY c.date ASC

我花了几天时间阅读这篇文章并且我找不到任何好的解决方案。有人建议LIMIT可以在子查询中工作(LIMIT在子查询中支持当前版本的MySQL),但LIMIT适用于返回集,而不是进入聚合的行,因此添加它没有任何区别。

我也不能使用LIMIT编写非聚合SELECT,然后对其进行聚合,因为在FROM语句中不允许使用相关子查询。所以这(遗憾地)不起作用:

SELECT  c.date
,       SELECT AVERAGE(last_5.value)
        FROM (  SELECT  m.value
                FROM    measures as m
                WHERE   m.measured_on_dt <= c.date
                ORDER BY m.measured_on_dt DESC
                LIMIT 5
              ) as `last_5`
FROM    calendar c
WHERE   c.date between date1 AND date2  -- graph boundaries
ORDER BY c.date ASC

我认为我需要完全避免使用子查询方法,看看我是否使用带有用户变量的聪明连接/行编号技术然后聚合,但是当我正在研究时我认为我是问是否有人知道更好的方法?

更新: 好的,我有一个解决方案,我已经为这个例子简化了。它依赖于一些用户变量技巧来从日历日期向后编号。它还与日历表(而不是子查询)进行交叉产品,但这会产生令人遗憾的副作用,导致行编号技巧失败(用户变量在发送到客户端时进行评估,而不是在该行被评估)所以为了解决这个问题,我必须将查询嵌套一个级别,对结果进行排序,然后将行编号技巧应用于该集合,然后该集合起作用。

此查询仅返回有度量的日历日期,因此如果您想要整个时间轴,只需选择日历并将LEFT JOIN添加到此结果集。

set @day = 0;
set @num = 0;
set @LIMIT = 5;

SELECT  date
,       AVG(value) as recent_N_AVG
FROM
(  SELECT *
  ,      @num := if(@day = c.date, @num + 1, 1) as day_row_number
  ,      @day := day as dummy
  FROM 
  ( SELECT  c.full_date
    ,       m.value
    ,       m.measured_on_dt
    FROM    calendar c 
    JOIN    measures as m
    WHERE   m.measured_on_dt <= c.full_date
    AND     c.full_date BETWEEN date1 AND date2  
    ORDER BY c.full_date ASC, measured_on_dt DESC
  ) as full_data
) as numbered
WHERE day_row_number <= @LIMIT
GROUP BY date

行编号技巧可以推广到更复杂的数据(我的测量有几个方面需要聚合)。

1 个答案:

答案 0 :(得分:0)

如果您的时间表是连续的(每天1个值),您可以改善您的第一次尝试:

SELECT c.date,
       ( SELECT AVERAGE(m.value) 
         FROM   measures as m
         WHERE  m.measured_on_dt 
                    BETWEEN DATE_SUB(c.date, INTERVAL 5 day) AND c.date
       ) as `average_to_date`
FROM    calendar c
WHERE   c.date between date1 AND date2  -- graph boundaries
ORDER BY c.date ASC

如果你的时间轴上有洞,这将导致平均值少于5个值。