4

我有一个简单的 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 的行?

4

2 回答 2

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 演示:http ://sqlfiddle.com/#!2/52420/2

跟进:

问:我试图理解您的 SQL 语句。您的解决方案是否为数据库中的每一行选择二十行?换句话说,如果我有 1000 行,您的语句会执行 20000 次选择吗?(我担心性能)...

A:你关心性能是对的。

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

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

一旦创建并填充了两个“派生”表,MySQL 就会运行外部查询,使用这两个“派生”表作为行源。, 中的每一行都与 rq中的最多行匹配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
于 2013-02-21T00:24:49.897 回答
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 小提琴演示

产生以下内容:

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 子句。这是更新的小提琴

于 2013-02-21T00:05:37.410 回答