mysql移动平均N行

时间:2013-02-21 00:00:35

标签: mysql select moving-average

我有一个简单的MySQL表,如下所示,用于计算汽车的MPG。

+-------------+-------+---------+
| DATE        | MILES | GALLONS |
+-------------+-------+---------+
| JAN 25 1993 |  20.0 |    3.00 |
| FEB 07 1993 |  55.2 |    7.22 |
| MAR 11 1993 |  44.1 |    6.28 |
+-------------+-------+---------+

我可以使用select语句轻松计算汽车的每加仑里程数(MPG),但因为MPG从填充到填充变化很大(即每次都没有填充相同数量的汽油),想把电脑变成'移动平均'。因此对于任何行,MPG是该行的MILES / GALLON,MOVINGMPG是最后N行的SUM(MILES)/ SUM(GALLONS)。如果该点存在少于N行,则只有SUM(MILES)/ SUM(GALLONS)直到那一点。

是否有一个SELECT语句通过将N替换为select语句来获取MPG和MOVINGMPG的行?

2 个答案:

答案 0 :(得分:3)

是的,可以使用单个SQL语句返回指定的结果集。

不幸的是,MySQL不支持分析函数,这会产生一个相当简单的语句。即使MySQL没有支持它们的语法,也可以使用MySQL用户变量模拟一些分析函数。

实现指定结果集(使用单个SQL语句)的一种方法是使用JOIN操作,使用唯一的升序整数值(rownum,由查询派生并在查询中分配)到每一行。

例如:

SELECT q.rownum          AS rownum
     , q.date            AS latest_date
     , q.miles/q.gallons AS latest_mpg
     , COUNT(1)               AS cnt_rows
     , MIN(r.date)            AS earliest_date
     , SUM(r.miles)                AS rtot_miles
     , SUM(r.gallons)              AS rtot_gallons
     , SUM(r.miles)/SUM(r.gallons) AS rtot_mpg
  FROM ( SELECT @s_rownum := @s_rownum + 1 AS rownum
              , s.date
              , s.miles
              , s.gallons
           FROM mytable s
           JOIN (SELECT @s_rownum := 0) c
          ORDER BY s.date
       ) q
  JOIN ( SELECT @t_rownum := @t_rownum + 1 AS rownum
              , t.date                  
              , t.miles
              , t.gallons
           FROM mytable t
           JOIN (SELECT @t_rownum := 0) d
          ORDER BY t.date
       ) r
    ON r.rownum <= q.rownum
   AND r.rownum > q.rownum - 2
 GROUP BY q.rownum

在“GROUP BY子句之前的谓词中指定了所需的”n“值,以指定每个汇总行中要包含的行数。在此示例中,每个运行总行中最多为“2”行。

如果指定值1,您将(基本上)获得返回的原始表。

要消除任何“不完整”的运行总行数(由少于“n”行组成),需要再次指定“n”的值,添加:

HAVING COUNT(1) >= 2

sqlfiddle demo:http://sqlfiddle.com/#!2/52420/2

跟进:

问:我正在尝试理解您的SQL语句。您的解决方案是否为数据库中的每一行选择了20行?换句话说,如果我有1000行,你的语句会执行20000次选择吗? (我担心表现)......

答:关注绩效你是对的。

要回答你的问题,不,这不会为1,000行执行20,000次选择。

性能影响来自两个(基本相同的)内联视图(别名为qr)。 MySQL对这些(基本上)做的是创建临时MyISAM表(MySQL称之为“派生表”),它们基本上是mytable的副本,带有一个额外的列,每行分配一个从1到1的唯一整数值行数。

一旦创建并填充了两个“派生”表,MySQL就会运行外部查询,使用这两个“派生”表作为行源。来自q的每一行与来自r的最多n行匹配,以计算“运行总计”里程和加仑。

为了获得更好的性能,您可以使用表中已有的列,而不是让查询指定唯一的整数值。例如,如果date列是唯一的,那么您可以计算一段时间内的“运行总计”。

SELECT q.date                      AS latest_date
     , SUM(q.miles)/SUM(q.gallons) AS latest_mpg
     , COUNT(1)                    AS cnt_rows
     , MIN(r.date)                 AS earliest_date
     , SUM(r.miles)                AS rtot_miles
     , SUM(r.gallons)              AS rtot_gallons
     , SUM(r.miles)/SUM(r.gallons) AS rtot_mpg
  FROM mytable q
  JOIN mytable r
    ON r.date <= q.date
   AND r.date > q.date + INTERVAL -30 DAY
 GROUP BY q.date

(为了提高性能,您需要使用date定义的适当索引作为索引中的前导列。)


对于第一个查询,包含(在内联视图定义查询中)的任何谓词以减少返回的行数(例如,仅返回过去一年中的日期值)将减少要处理的行数,并且也可能会提高绩效。


再次,关于为1,000行运行20,000次选择的问题...嵌套循环操作是获得相同结果集的另一种方法。对于大量行,这可能表现出较慢的性能。 (另一方面,当只返回几行时,这种方法可以相当有效:

SELECT q.date                 AS latest_date
     , q.miles/q.gallons      AS latest_mpg
     , ( SELECT SUM(r.miles)/SUM(r.gallons)
           FROM mytable r
          WHERE r.date <= q.date
            AND r.date >= q.date + INTERVAL -90 DAY
       ) AS rtot_mpg
  FROM mytable q
 ORDER BY q.date

答案 1 :(得分:0)

这样的事情应该有效:

SELECT Date, Miles, Gallons, Miles/Gallons as MilesPerGallon,
  @Miles:=@Miles+Miles overallMiles,
  @Gallons:=@Gallons+Gallons overallGallons,
  @RunningTotal:=@Miles/@Gallons runningTotal
FROM YourTable
  JOIN (SELECT @Miles:= 0) t
  JOIN (SELECT @Gallons:= 0) s

SQL Fiddle Demo

产生以下内容:

DATE                MILES    GALLONS    MILESPERGALLON   RUNNINGTOTAL
January, 25 1993    20       3          6.666667         6.666666666667
February, 07 1993   55.2     7.22       7.645429         7.358121330724
March, 11 1993      44.1     6.28       7.022293         7.230303030303

- 编辑 -

在回复评论时,您可以添加另一个行号以将结果限制为最后N行:

SELECT *
FROM (
  SELECT Date, Miles, Gallons, Miles/Gallons as MilesPerGallon,
    @Miles:=@Miles+Miles overallmiles,
    @Gallons:=@Gallons+Gallons overallGallons,
    @RunningTotal:=@Miles/@Gallons runningTotal,
    @RowNumber:=@RowNumber+1 rowNumber
  FROM (SELECT * FROM YourTable ORDER BY Date DESC) u
    JOIN (SELECT @Miles:= 0) t
    JOIN (SELECT @Gallons:= 0) s
    JOIN (SELECT @RowNumber:= 0) r
  ) t
WHERE rowNumber <= 3

只需相应地更改您的ORDER BY子句。这是updated fiddle