如何在mysql中查询每个分组的前几名

   2016-12-23 0
核心提示:#问题#在工作中常会遇到将数据分组排序的问题,如在考试成绩中,找出每个班级的前五名等。在orcale等数据库中可以使用partition 语句来解决,但在mysql中就比较麻烦了。这次翻译的文章就是专门解决这个问题的原文地址: [How to select the first/least/max ro

#问题#
在工作中常会遇到将数据分组排序的问题,如在考试成绩中,找出每个班级的前五名等。
在orcale等数据库中可以使用partition 语句来解决,但在mysql中就比较麻烦了。这次翻译的文章就是专门解决这个问题的

原文地址: [How to select the first/least/max row per group in SQL](http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/)

#翻译#

在使用SQL的过程中,我们经常遇到这样一类问题:如何找出每个程序最近的日志条目?如何找出每个用户的最高分?在每个分类中最受欢迎的商品是什么?通常这类“找出每个分组中最高分的条目”的问题可以使用相同的技术来解决。在这篇文章里我将介绍如何解决这类问题,而且会介绍如何找出最高的前几名而不仅仅是第一名。

这篇文章会用到**行数**(row number),我在原来的文章 [MySQL-specific](http://www.xaprb.com/blog/2006/12/02/how-to-number-rows-in-mysql/) 和 [generic techniques](http://www.xaprb.com/blog/2005/09/27/simulating-the-sql-row_number-function/) 中已经提到过如何为每个分组设置**行数**了。在这里我会使用与原来的文章中相同的表格,但会加入新的price 字段

+--------+------------+-------+
| type | variety | price |
+--------+------------+-------+
| apple | gala | 2.79 |
| apple | fuji | 0.24 |
| apple | limbertwig | 2.87 |
| orange | valencia | 3.59 |
| orange | navel | 9.36 |
| pear | bradford | 6.05 |
| pear | bartlett | 2.14 |
| cherry | bing | 2.55 |
| cherry | chelan | 6.33 |
+--------+------------+-------+

##选择每个分组中的最高分##
这里我们要说的是如何找出每个程序最新的日志记录或审核表中最近的更新或其他类似的排序问题。这类问题在IRC频道和邮件列表中出现的越来越频繁。我使用水果问题来作为示例,在示例中我们要选出每类水果中最便宜的一个,我们期望的结果如下

+--------+----------+-------+
| type | variety | price |
+--------+----------+-------+
| apple | fuji | 0.24 |
| orange | valencia | 3.59 |
| pear | bartlett | 2.14 |
| cherry | bing | 2.55 |
+--------+----------+-------+

这个问题有几种解法,但基本上就是这两步:找出最低的价格,然后找出和这个价格同一行的其他数据

其中一个常用的方法是使用自连接(self-join),第一步根据type(apple, cherry etc)进行分组,并找出每组中price的最小值

select type, min(price) as minprice
from fruits
group by type;
+--------+----------+
| type | minprice |
+--------+----------+
| apple | 0.24 |
| cherry | 2.55 |
| orange | 3.59 |
| pear | 2.14 |
+--------+----------+

第二步是将刚刚结果与原来的表进行连接。既然刚刚给结果已经被分组了,我们将刚刚的查询语句作为子查询以便于连接没有被分组的原始表格。

select f.type, f.variety, f.price
from (
select type, min(price) as minprice
from fruits group by type
) as x inner join fruits as f on f.type = x.type and f.price = x.minprice;

+--------+----------+-------+
| type | variety | price |
+--------+----------+-------+
| apple | fuji | 0.24 |
| cherry | bing | 2.55 |
| orange | valencia | 3.59 |
| pear | bartlett | 2.14 |
+--------+----------+-------+

还可以使用相关子查询(correlated subquery)的方式来解决。这种方法在不同的mysql优化系统下,可能性能会有一点点下降,但这种方法会更直观一些。

select type, variety, price
from fruits
where price = (select min(price) from fruits as f where f.type = fruits.type);
+--------+----------+-------+
| type | variety | price |
+--------+----------+-------+
| apple | fuji | 0.24 |
| orange | valencia | 3.59 |
| pear | bartlett | 2.14 |
| cherry | bing | 2.55 |
+--------+----------+-------+

这两种查询在逻辑上是一样的,他们性能也基本相同

##找出每组中前N个值##
这个问题会稍微复杂一些。我们可以使用聚集函数(MIN(), MAX()等等)来找一行,但是找前几行不能直接使用这些函数,因为它们都只返回一个值。但这个问题还是可以解决的。

这次我们找出每个类型(type)中最便宜的前两种水果,首先我们尝试

select type, variety, price
from fruits
where price = (select min(price) from fruits as f where f.type = fruits.type)
or price = (select min(price) from fruits as f where f.type = fruits.type
and price > (select min(price) from fruits as f2 where f2.type = fruits.type));
+--------+----------+-------+
| type | variety | price |
+--------+----------+-------+
| apple | gala | 2.79 |
| apple | fuji | 0.24 |
| orange | valencia | 3.59 |
| orange | navel | 9.36 |
| pear | bradford | 6.05 |
| pear | bartlett | 2.14 |
| cherry | bing | 2.55 |
| cherry | chelan | 6.33 |
+--------+----------+-------+

是的,我们可以写成自连接(self-join)的形式,但是仍不够好(我将这个练习留给读者)。这种方式在N变大(前三名,前4名)的时候性能会越来越差。我们可以使用其他的表现形式编写这个查询,但是它们都不够好,它们都相当的笨重和效率低下。(**译者注**:这种方式获取的结果时,如果第N个排名是重复的时候最后选择的结果会超过N,比如上面例子还有一个apple价格也是0.24,那最后的结果就会有3个apple)

我们有一种稍好的方式,在每个种类中选择不超过该种类第二便宜的水果

select type, variety, price
from fruits
where (
select count(*) from fruits as f
where f.type = fruits.type and f.price<= fruits.price
)<= 2;

这次的代码要优雅很多,而且在N增加时不需要重新代码(非常棒!)。但是这个查询在功能上和原来的是一样。他们的时间复杂度均为分组中条目数的二次方。而且,很多优化器都不能优化这种查询,使得它的耗时最好为全表行数的二次方(尤其在没有设置正确的索引时),而且数据量大时,可能将服务器会停止响应。那么还有更好的方法吗?有没有办法可以仅仅扫描一次数据,而不是通过子查询进行多次扫描。(**译者注**:这种方法有一个问题,就是如果排名并列第一的数字超过N后,这个分组会选不出数据,比如price为2.79的apple有3个,那么结果中就没有apple了)

##使用 UNION##
如果已经为type, price设置了索引,而且在每个分组中去除的数据要多于包含的数据,一种非常高效的单次扫描的方法是将查询拆分成多个独立的查询(尤其对mysql,对其他的RDBMSs也有效),再使用UNION将结果拼到一起。mysql的写法如下:

(select * from fruits where type = 'apple' order by price limit 2)
union all
(select * from fruits where type = 'orange' order by price limit 2)
union all
(select * from fruits where type = 'pear' order by price limit 2)
union all
(select * from fruits where type = 'cherry' order by price limit 2)

[Peter Zaistev写了相关的文章](http://www.mysqlperformanceblog.com/2006/08/10/using-union-to-implement-loose-index-scan-to-mysql/), 我在这里就不赘述了。如果这个方案满足你的要求,那它就是一个非常好的选择.

注意:这里要使用UNION ALL,而不是UNION。后者会在合并的时候会将重复的条目清除掉。在我们的这个示例中没有去除重复的需求,所以我们告诉服务器不要清除重复,清除重复在这个问题中是无用的,而且会造成性能的大幅下降。

##使用用户自定义变量##
但结果是数据表中很小一部分条目并且有索引用来排序的时候,使用UNION的方式是一个很好的选择。而当你要获取数据表中大部分条目时也有一种能达到线性时间的方法,那就是使用用户定义变量。这里我将介绍的仅仅是mysql中的用法。在我原来的博客[在mysql中,如何为条目编号(How to number rows in MySQL)](http://www.xaprb.com/blog/2006/12/02/how-to-number-rows-in-mysql/)里介绍了它是怎么工作的:

set @num := 0, @type := '';
select type, variety, price
from (
select type, variety, price,
@num := if(@type = type, @num + 1, 1) as row_number,
@type := type as dummy
from fruits
order by type, price
) as x where x.row_number<= 2;

这个方法并不仅仅做单次扫描,子查询在后台创建临时表,然后通过一次扫描将数据填充进去,然后在临时表中选择数据用于主查询的WHERE语句。但即使是两次扫描,它的时间复杂度仍是O(n),这里n是表示数据表的行数。它远比上面的相关子查询的结果O(n ^ 2)要好许多, 这里的n表示的是分组中平均条目数 - 即使是中等规模的数据也会造成极差的性能。(假设每种水果中有5 varitey,那么就需要25次扫描)

##在MySQL中一次扫描的方法##
如果你无法放弃你头脑中优化查询的想法,你可以试试这个方法,它不使用临时表,并且只做一次扫描

set @num := 0, @type := '';

select type, variety, price,
@num := if(@type = type, @num + 1, 1) as row_number,
@type := type as dummy
from fruits
group by type, price, variety
having row_number<= 2;

只要MySQL的GROUP BY语句符合标准,这个方式在理论上就是是可行。那么实际上可行吗?下面是我在MySQL 5.0.7的Windows 版上的结果

+--------+----------+-------+------------+--------+
| type | variety | price | row_number | dummy |
+--------+----------+-------+------------+--------+
| apple | gala | 2.79 | 1 | apple |
| apple | fuji | 0.24 | 3 | apple |
| orange | valencia | 3.59 | 1 | orange |
| orange | navel | 9.36 | 3 | orange |
| pear | bradford | 6.05 | 1 | pear |
| pear | bartlett | 2.14 | 3 | pear |
| cherry | bing | 2.55 | 1 | cherry |
| cherry | chelan | 6.33 | 3 | cherry |
+--------+----------+-------+------------+--------+

可以看到,这已经和结果很接近了。他返回了每个分组的第一行和第三行,结果并没有按照price的升序进行排列。当时HAVING 语句要求row_number不应当大于2。接下来是5.0.24a 在ubuntu上的结果:

+--------+------------+-------+------------+--------+
| type | variety | price | row_number | dummy |
+--------+------------+-------+------------+--------+
| apple | fuji | 0.24 | 1 | apple |
| apple | gala | 2.79 | 1 | apple |
| apple | limbertwig | 2.87 | 1 | apple |
| cherry | bing | 2.55 | 1 | cherry |
| cherry | chelan | 6.33 | 1 | cherry |
| orange | valencia | 3.59 | 1 | orange |
| orange | navel | 9.36 | 1 | orange |
| pear | bartlett | 2.14 | 1 | pear |
| pear | bradford | 6.05 | 1 | pear |
+--------+------------+-------+------------+--------+

这次,所有的row_number都是1,而且好像所有行都返回了。可以参考[MySQL手册用户自定义变量](http://dev.mysql.com/doc/refman/5.0/en/user-variables.html)。

使用这种技术的结果很难确定,主要是因为这里涉及的技术是你和我都不能直接接触的,例如MySQL在Group的时候使用哪个索引。如果你仍需要使用它 - 我知道很多人已经用了,因为我告诉了他们 - 你还是可以用的。我们正在进入SQL的真正领域,但是上面的结果是在没有设置索引的情况下得到的。我们现在看看了设置了索引之后group的结果是什么。

alter table fruits add key(type, price);

执行之后会发现没有什么变化,之后使用EXPLAIN查看查询过程,会发现此查询没有使用任何索引。这是为什么呢?因为Group使用了3个字段,但是索引只有两个字段。实际上,查询仍使用了临时表,所有我们并没完成一次扫描的目标。我们可以强制使用索引:

set @num := 0, @type := '';

select type, variety, price,
@num := if(@type = type, @num + 1, 1) as row_number,
@type := type as dummy
from fruits force index(type)
group by type, price, variety
having row_number<= 2;

我们看一下是否起作用了。

+--------+----------+-------+------------+--------+
| type | variety | price | row_number | dummy |
+--------+----------+-------+------------+--------+
| apple | fuji | 0.24 | 1 | apple |
| apple | gala | 2.79 | 2 | apple |
| cherry | bing | 2.55 | 1 | cherry |
| cherry | chelan | 6.33 | 2 | cherry |
| orange | valencia | 3.59 | 1 | orange |
| orange | navel | 9.36 | 2 | orange |
| pear | bartlett | 2.14 | 1 | pear |
| pear | bradford | 6.05 | 2 | pear |
+--------+----------+-------+------------+--------+

现在我们得到了我们想要的结果了,而且没有文件排序(filesort)和临时表。还有一种方法就是将variety提出到GROUP BY之外,这样它就可以使用自己的索引。因为这个查询是一个[从分组中查询非分组字段](http://www.xaprb.com/blog/2006/03/11/many-to-one-problems-in-sql/)的查询,它只能在 ONLY_FULL_GROUP_BY 模式关闭([链接](http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html))的情况下才能起作用。但是在没有特殊原因的情况下,我不建议你这么做。

##其他方法##
可以在评论中看到其他的方法,里面有的确有一些非常梦幻的方法。我一直在你们的评论获取知识,感谢你们。

##总结##
我们这里介绍了集中方法去解决“每个分组中最大的条目”这类问题已经进一步扩展到查询每组中前N个条目的方法。之后我们深入探讨了一些MySQL特定的技术,这些技术看起来有一些傻和笨。但是如果你需要榨干服务器的最后一点性能,你就需要知道什么时候去打破规则。对于那些认为这是MySQL本身的问题的人,我要说这不是,我曾经看到过使用其他平台的人也在做着同样的事情,如SQL Server。在每个平台上都会有很多特殊的小技巧和花招,使用他们的人必须去适应它。

我希望你喜欢这篇文章并从中获益。如果确实是这样,你可能想要[订阅](http://www.xaprb.com/blog/subscribe/)我的博客。Happy Coding!

问题

在工作中常会遇到将数据分组排序的问题,如在考试成绩中,找出每个班级的前五名等。

在orcale等数据库中可以使用partition 语句来解决,但在mysql中就比较麻烦了。这次翻译的文章就是专门解决这个问题的

原文地址: How to select the first/least/max row per group in SQL

翻译

在使用SQL的过程中,我们经常遇到这样一类问题:如何找出每个程序最近的日志条目?如何找出每个用户的最高分?在每个分类中最受欢迎的商品是什么?通常这类“找出每个分组中最高分的条目”的问题可以使用相同的技术来解决。在这篇文章里我将介绍如何解决这类问题,而且会介绍如何找出最高的前几名而不仅仅是第一名。

这篇文章会用到 行数 (row number),我在原来的文章 MySQL-specificgeneric techniques 中已经提到过如何为每个分组设置 行数 了。在这里我会使用与原来的文章中相同的表格,但会加入新的price 字段

+--------+------------+-------+
| type   | variety    | price |
+--------+------------+-------+
| apple  | gala       |  2.79 | 
| apple  | fuji       |  0.24 | 
| apple  | limbertwig |  2.87 | 
| orange | valencia   |  3.59 | 
| orange | navel      |  9.36 | 
| pear   | bradford   |  6.05 | 
| pear   | bartlett   |  2.14 | 
| cherry | bing       |  2.55 | 
| cherry | chelan     |  6.33 | 
+--------+------------+-------+

选择每个分组中的最高分

这里我们要说的是如何找出每个程序最新的日志记录或审核表中最近的更新或其他类似的排序问题。这类问题在IRC频道和邮件列表中出现的越来越频繁。我使用水果问题来作为示例,在示例中我们要选出每类水果中最便宜的一个,我们期望的结果如下

+--------+----------+-------+
| type   | variety  | price |
+--------+----------+-------+
| apple  | fuji     |  0.24 | 
| orange | valencia |  3.59 | 
| pear   | bartlett |  2.14 | 
| cherry | bing     |  2.55 | 
+--------+----------+-------+

这个问题有几种解法,但基本上就是这两步:找出最低的价格,然后找出和这个价格同一行的其他数据

其中一个常用的方法是使用自连接(self-join),第一步根据type(apple, cherry etc)进行分组,并找出每组中price的最小值

select type, min(price) as minprice
from fruits
group by type;
+--------+----------+
| type   | minprice |
+--------+----------+
| apple  |     0.24 | 
| cherry |     2.55 | 
| orange |     3.59 | 
| pear   |     2.14 | 
+--------+----------+

第二步是将刚刚结果与原来的表进行连接。既然刚刚给结果已经被分组了,我们将刚刚的查询语句作为子查询以便于连接没有被分组的原始表格。

select f.type, f.variety, f.price
from (
   select type, min(price) as minprice
   from fruits group by type
) as x inner join fruits as f on f.type = x.type and f.price = x.minprice;

+--------+----------+-------+
| type   | variety  | price |
+--------+----------+-------+
| apple  | fuji     |  0.24 | 
| cherry | bing     |  2.55 | 
| orange | valencia |  3.59 | 
| pear   | bartlett |  2.14 | 
+--------+----------+-------+

还可以使用相关子查询(correlated subquery)的方式来解决。这种方法在不同的mysql优化系统下,可能性能会有一点点下降,但这种方法会更直观一些。

select type, variety, price
from fruits
where price = (select min(price) from fruits as f where f.type = fruits.type);
+--------+----------+-------+
| type   | variety  | price |
+--------+----------+-------+
| apple  | fuji     |  0.24 | 
| orange | valencia |  3.59 | 
| pear   | bartlett |  2.14 | 
| cherry | bing     |  2.55 | 
+--------+----------+-------+

这两种查询在逻辑上是一样的,他们性能也基本相同

找出每组中前N个值

这个问题会稍微复杂一些。我们可以使用聚集函数(MIN(), MAX()等等)来找一行,但是找前几行不能直接使用这些函数,因为它们都只返回一个值。但这个问题还是可以解决的。

这次我们找出每个类型(type)中最便宜的前两种水果,首先我们尝试

select type, variety, price
from fruits
where price = (select min(price) from fruits as f where f.type = fruits.type)
   or price = (select min(price) from fruits as f where f.type = fruits.type
      and price > (select min(price) from fruits as f2 where f2.type = fruits.type));
+--------+----------+-------+
| type   | variety  | price |
+--------+----------+-------+
| apple  | gala     |  2.79 | 
| apple  | fuji     |  0.24 | 
| orange | valencia |  3.59 | 
| orange | navel    |  9.36 | 
| pear   | bradford |  6.05 | 
| pear   | bartlett |  2.14 | 
| cherry | bing     |  2.55 | 
| cherry | chelan   |  6.33 | 
+--------+----------+-------+

是的,我们可以写成自连接(self-join)的形式,但是仍不够好(我将这个练习留给读者)。这种方式在N变大(前三名,前4名)的时候性能会越来越差。我们可以使用其他的表现形式编写这个查询,但是它们都不够好,它们都相当的笨重和效率低下。( 译者注 :这种方式获取的结果时,如果第N个排名是重复的时候最后选择的结果会超过N,比如上面例子还有一个apple价格也是0.24,那最后的结果就会有3个apple)

我们有一种稍好的方式,在每个种类中选择不超过该种类第二便宜的水果

select type, variety, price
from fruits
where (
   select count(*) from fruits as f
   where f.type = fruits.type and f.price <= fruits.price
) <= 2;

这次的代码要优雅很多,而且在N增加时不需要重新代码(非常棒!)。但是这个查询在功能上和原来的是一样。他们的时间复杂度均为分组中条目数的二次方。而且,很多优化器都不能优化这种查询,使得它的耗时最好为全表行数的二次方(尤其在没有设置正确的索引时),而且数据量大时,可能将服务器会停止响应。那么还有更好的方法吗?有没有办法可以仅仅扫描一次数据,而不是通过子查询进行多次扫描。( 译者注 :这种方法有一个问题,就是如果排名并列第一的数字超过N后,这个分组会选不出数据,比如price为2.79的apple有3个,那么结果中就没有apple了)

使用 UNION

如果已经为type, price设置了索引,而且在每个分组中去除的数据要多于包含的数据,一种非常高效的单次扫描的方法是将查询拆分成多个独立的查询(尤其对mysql,对其他的RDBMSs也有效),再使用UNION将结果拼到一起。mysql的写法如下:

(select * from fruits where type = 'apple' order by price limit 2)
union all
(select * from fruits where type = 'orange' order by price limit 2)
union all
(select * from fruits where type = 'pear' order by price limit 2)
union all
(select * from fruits where type = 'cherry' order by price limit 2)

Peter Zaistev写了相关的文章 , 我在这里就不赘述了。如果这个方案满足你的要求,那它就是一个非常好的选择.

注意:这里要使用UNION ALL,而不是UNION。后者会在合并的时候会将重复的条目清除掉。在我们的这个示例中没有去除重复的需求,所以我们告诉服务器不要清除重复,清除重复在这个问题中是无用的,而且会造成性能的大幅下降。

使用用户自定义变量

但结果是数据表中很小一部分条目并且有索引用来排序的时候,使用UNION的方式是一个很好的选择。而当你要获取数据表中大部分条目时也有一种能达到线性时间的方法,那就是使用用户定义变量。这里我将介绍的仅仅是mysql中的用法。在我原来的博客 在mysql中,如何为条目编号(How to number rows in MySQL) 里介绍了它是怎么工作的:

set @num := 0, @type := '';
select type, variety, price
from (
   select type, variety, price,
      @num := if(@type = type, @num + 1, 1) as row_number,
      @type := type as dummy
  from fruits
  order by type, price
) as x where x.row_number <= 2;

这个方法并不仅仅做单次扫描,子查询在后台创建临时表,然后通过一次扫描将数据填充进去,然后在临时表中选择数据用于主查询的WHERE语句。但即使是两次扫描,它的时间复杂度仍是O(n),这里n是表示数据表的行数。它远比上面的相关子查询的结果O(n ^ 2)要好许多, 这里的n表示的是分组中平均条目数 - 即使是中等规模的数据也会造成极差的性能。(假设每种水果中有5 varitey,那么就需要25次扫描)

在MySQL中一次扫描的方法

如果你无法放弃你头脑中优化查询的想法,你可以试试这个方法,它不使用临时表,并且只做一次扫描

set @num := 0, @type := '';

select type, variety, price,
      @num := if(@type = type, @num + 1, 1) as row_number,
      @type := type as dummy
from fruits
group by type, price, variety
having row_number <= 2;

只要MySQL的GROUP BY语句符合标准,这个方式在理论上就是是可行。那么实际上可行吗?下面是我在MySQL 5.0.7的Windows 版上的结果

+--------+----------+-------+------------+--------+
| type   | variety  | price | row_number | dummy  |
+--------+----------+-------+------------+--------+
| apple  | gala     |  2.79 |          1 | apple  |
| apple  | fuji     |  0.24 |          3 | apple  |
| orange | valencia |  3.59 |          1 | orange |
| orange | navel    |  9.36 |          3 | orange |
| pear   | bradford |  6.05 |          1 | pear   |
| pear   | bartlett |  2.14 |          3 | pear   |
| cherry | bing     |  2.55 |          1 | cherry |
| cherry | chelan   |  6.33 |          3 | cherry |
+--------+----------+-------+------------+--------+

可以看到,这已经和结果很接近了。他返回了每个分组的第一行和第三行,结果并没有按照price的升序进行排列。当时HAVING 语句要求row_number不应当大于2。接下来是5.0.24a 在ubuntu上的结果:

+--------+------------+-------+------------+--------+
| type   | variety    | price | row_number | dummy  |
+--------+------------+-------+------------+--------+
| apple  | fuji       |  0.24 |          1 | apple  |
| apple  | gala       |  2.79 |          1 | apple  |
| apple  | limbertwig |  2.87 |          1 | apple  |
| cherry | bing       |  2.55 |          1 | cherry |
| cherry | chelan     |  6.33 |          1 | cherry |
| orange | valencia   |  3.59 |          1 | orange |
| orange | navel      |  9.36 |          1 | orange |
| pear   | bartlett   |  2.14 |          1 | pear   |
| pear   | bradford   |  6.05 |          1 | pear   |
+--------+------------+-------+------------+--------+

这次,所有的row_number都是1,而且好像所有行都返回了。可以参考 MySQL手册用户自定义变量

使用这种技术的结果很难确定,主要是因为这里涉及的技术是你和我都不能直接接触的,例如MySQL在Group的时候使用哪个索引。如果你仍需要使用它 - 我知道很多人已经用了,因为我告诉了他们 - 你还是可以用的。我们正在进入SQL的真正领域,但是上面的结果是在没有设置索引的情况下得到的。我们现在看看了设置了索引之后group的结果是什么。

alter table fruits add key(type, price);

执行之后会发现没有什么变化,之后使用EXPLAIN查看查询过程,会发现此查询没有使用任何索引。这是为什么呢?因为Group使用了3个字段,但是索引只有两个字段。实际上,查询仍使用了临时表,所有我们并没完成一次扫描的目标。我们可以强制使用索引:

set @num := 0, @type := '';

select type, variety, price,
      @num := if(@type = type, @num + 1, 1) as row_number,
      @type := type as dummy
from fruits force index(type)
group by type, price, variety
having row_number <= 2;

我们看一下是否起作用了。

+--------+----------+-------+------------+--------+
| type   | variety  | price | row_number | dummy  |
+--------+----------+-------+------------+--------+
| apple  | fuji     |  0.24 |          1 | apple  | 
| apple  | gala     |  2.79 |          2 | apple  | 
| cherry | bing     |  2.55 |          1 | cherry | 
| cherry | chelan   |  6.33 |          2 | cherry | 
| orange | valencia |  3.59 |          1 | orange | 
| orange | navel    |  9.36 |          2 | orange | 
| pear   | bartlett |  2.14 |          1 | pear   | 
| pear   | bradford |  6.05 |          2 | pear   | 
+--------+----------+-------+------------+--------+

现在我们得到了我们想要的结果了,而且没有文件排序(filesort)和临时表。还有一种方法就是将variety提出到GROUP BY之外,这样它就可以使用自己的索引。因为这个查询是一个 从分组中查询非分组字段 的查询,它只能在 ONLY_FULL_GROUP_BY 模式关闭( 链接 )的情况下才能起作用。但是在没有特殊原因的情况下,我不建议你这么做。

其他方法

可以在评论中看到其他的方法,里面有的确有一些非常梦幻的方法。我一直在你们的评论获取知识,感谢你们。

总结

我们这里介绍了集中方法去解决“每个分组中最大的条目”这类问题已经进一步扩展到查询每组中前N个条目的方法。之后我们深入探讨了一些MySQL特定的技术,这些技术看起来有一些傻和笨。但是如果你需要榨干服务器的最后一点性能,你就需要知道什么时候去打破规则。对于那些认为这是MySQL本身的问题的人,我要说这不是,我曾经看到过使用其他平台的人也在做着同样的事情,如SQL Server。在每个平台上都会有很多特殊的小技巧和花招,使用他们的人必须去适应它。

https://my.oschina.net/u/1032146/blog/149300

 
标签: MySQL PEAR
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • Perl操作Mysql数据库 perl操作excel
    一. 安装DBI模块步骤1:从TOOLS栏目中下载DBI.zip,下载完后用winzip解开到一个temp目录,共有三个文件:ReadmeDBI.ppdDBI.tar.gz步骤2: 在DOS窗口下,temp目录中运行下面的DOS命令:ppm install DBI.ppd 如果提示无效命令,可在perl/bin目录下运行 二. 安装DBD
    02-09
  • Nodejs+Express+Mysql实现简单用户管理增删改查
    Nodejs+Express+Mysql实现简单用户管理增删改查
     源码地址  https://github.com/king-y/NodeJs/tree/master/user目录结构  mysql.jsvar mysql = require('mysql');var pool = mysql.createPool({host : '127.0.0.1',user : 'root',password : '',database : 's79'});exports.que
    02-09
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
    PHP 使用 Swoole - TaskWorker 实现异步操作 My
    在一般的 Server 程序中都会有一些耗时的任务,比如:发送邮件、聊天服务器发送广播等。如果我们采用同步阻塞的防水去执行这些任务,那么这肯定会非常的慢。Swoole 的 TaskWorker 进程池可以用来执行一些异步的任务,而且不会影响接下来的任务,很适合处理以
    02-09
  • Mysql数据库一个小程序实现自动创建分表。
    每当跨月的时候也是系统出问题最多的时候,没有表和字段缺失是两个最常见的错误。为了解决这个问题,研究了一下mysql的 information_schema 表:information_schema这张数据表保存了MySQL服务器所有数据库的信息。如数据库名,数据库的表,表栏的数据类型与访
    02-09
  • delphi10.3安装使用mySQL及遇到的问题(01)
    delphi10.3安装使用mySQL及遇到的问题(01)
    1】下载安装好mySQL环境2】FDConnection1的设置3】执行增删改SQL语句和查询Select语句4】客户端连接5]长文本类型,及SQLITE导出,导入到MySQL6】遇到过的问题及解决1】下载安装好mySQL环境,注意32位/64位。本篇为32位mysql32位下载链接: https://pan.baidu.c
    02-09
  • Delphi XE中使用dbExpress连接MySQL数据库疑难
    Delphi IDE中包含一个Data Explorer的组件,如下图所示:  该组件基于dbExpress(包含TSQLConnection、TSQLDataSet、TSQLQuery、TSQLStoredProc、TSQLTable、TsqlServerMethod、TSQLMonitor、TSimpleDataSet)。但是因为该组件只提供了各种数据库的抽象驱动
    02-09
  • DELPHI 10.2 TOKYO搭建LINUX MYSQL开发环境
    DELPHI 10.2 TOKYO搭建LINUX MYSQL开发环境笔者使用ubuntu64位LINUX首先必须保证LINUX可以连互联网。安装MYSQLsudo apt-get update sudo apt-get install mysql-serversudo apt-get install mysql-clientsudo apt-get install libmysqlclient-dev复制MYSQL的
    02-09
  • Centos7上安装MySQL5.5报错,/usr/bin/perl is
    今天在Centos7上安装MySQL5.5时报了如下错/usr/bin/perl is needed by MySQL-server-...按照网上的几种说法进行了尝试1、在perl官网下载perl后安装到相应的目录下,仍无法解决问题2、采用强制安装 rpm -ivh MySQL-server-5.6.22-1.el6.i686.rpm --nodeps 
    02-09
  • 用perl做数据库迁移,从MSSQL到MYSQL(三)--V1
    从前边的程序的运行情况来看,程序是可以运行的,但速度太扯了,在读写1000W条之前速度还是可以的(大概2000条/秒左右),但过了1000W之后(变成400条/秒左右),当然这个与SQL SERVER读取,网络还有服务器等性能都是有关系的,但,这速度,不晓得有测试过的
    02-09
  • R语言连接MYSQL r语言连接数据库
     操作系统:centos 6.4 64bit R语言可以使用RMySQL来连接Mysql数据库,直接使用数据库里面的数据生成图像。 这个是RMYSQL的说明:http://cran.r-project.org/web/packages/RMySQL/index.htmlRMYSQL的依赖:Depends:R (≥ 2.8.0), methods, DBI (≥ 0.2-2),
    02-09
点击排行