我有一个简单的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的行?
答案 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次选择。
性能影响来自两个(基本相同的)内联视图(别名为q
和r
)。 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
产生以下内容:
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。