现在有以下场景:

列出每个人最高的分数(用一条SQL来实现)。其表结构及部分模拟数据如下:

insert into
  tb
values
  ('1', 'Tom', 80, '2017-04-12'),
  ('2', 'Li', 69, '2017-04-12'),
  ('3', 'Queue', 99, '2017-04-12'),
  ('4', 'Tom', 78, '2017-04-20'),
  ('5', 'Li', 60, '2017-04-20'),
  ('6', 'Queue', 91, '2017-04-20'),
  ('7', 'Tom', 70, '2017-05-01'),
  ('8', 'Li', 73, '2017-05-01'),
  ('9', 'Queue', 88, '2017-05-01');

Id

Name

Score

Time

1

Tom

80

2017-04-12

2

Li

69

2017-04-12

3

Queue

99

2017-04-12

4

Tom

78

2017-04-20

5

Li

60

2017-04-20

6

Queue

91

2017-04-20

7

Tom

70

2017-05-01

8

Li

73

2017-05-01

9

Queue

88

2017-05-01

其正确结果应该是:

Id

Name

Score

Time

8

Li

73

2017-05-01

3

Queue

80

2017-04-12

1

Tom

99

2017-04-12

我们脑中首先想到的是先按照Name值进行分组(group by),然后根据每组的分数进行降序排序(order by),取出每组第一个就OK了(翻译成SQL:select * from tb group by Name order by Score)。

上面的思路看似没毛病,但是!

Group by 虽然会根据条件对数据进行分组,每组可能有一条或者多条数据,但是group by 只会返回一个结果。所以一般group by 都是配合聚合函(max、sum、count等等)数一起用的,返回一个该组的汇总记录。如果是以下语句:

select * from tb group by Name;

数据库则会按照默认排序,返回每组的第一条数据,即以下三条:

Id

Name

Score

Time

2

Li

69

2017-04-12

1

Tom

80

2017-04-12

3

Queue

99

2017-04-12

那以下语句呢:

select *, max(Score) from tb group by Name;

结果会是:

Id

Name

Score

Time

max(Score)

2

Li

69

2017-04-12

73

3

Queue

99

2017-04-12

99

1

Tom

80

2017-04-12

80

为什么是这样的结果呢?因为select * 还是会根据默认排序输出,而max(Score)是取了每组最大的Score值。对于这种Sql一定要注意使用。

现在我们再来看之前的所想的语句:group by 会在order by 之前先执行,group by 会返回每组的第一条数据,而order by 会在group by 返回的结果集上进行排序,显然并没什么卵用。

那我们为什么会这么去想呢?其实这就是我们误以为order by 会在group by 分组后但还未返回的数据集上进行的排序操作,然后返回每组的第一条数据。

理想总是丰满的,现实总是残酷的。。。。

那我们该如何实现这个需求的?

这就是本文的标题:组内排序。

我可以想到的SQL大概有以下几个,结果集的顺序可能会不一样(这里默认一个人只有一个最高分,只取一个人的一个最高分。如果最高分有多个或者其他需求则根据下面的基本SQL进行改写):

1. select * from ( select * from tb order by Score desc) as tmp group by ;

2. select l.* from tb l inner join ( select max(Score) Score, Name from tb group by Name) r on l.Score = r.Score and  = ;

3. select l.* from tb l,( select Name, max(Score) Score from tb group by Name) r where  =  and l.Score = r.Score;

4. select * from tb as tmp group by Name, Score having Score = ( select max(Score) from tb where Name = );

5. select * from tb outTb where Score = ( select max(Score) from tb where Name = outTb.Name);

6. select * from tb outTb where exists ( select count(*) from tb where Name = outTb.Name and Score > outTb.Score having count(*) < 1);

7. select * from tb outTb where 1 > ( select count(*) from tb where Name = outTb.Name and Score > outTb.Score);

8. select * from tb outTb where not exists ( select 1 from tb where Name = outTb.Name and Score > outTb.Score);

9. ( select * from tb where Name = 'Tom' order by Score desc limit 1) union all ( select * from tb where Name = 'Queue' order by Score desc limit 1) union all ( select * from tb where Name = 'Li' order by Score desc limit 1)

下面将对上面的SQL进行详解:

第一条SQL: select * from ( select * from tb order by Score desc) as tmp group by ;

可能是我们想到的最直观的SQL,但是这个SQL是建立在group by以后返回的是每个分组的第一条数据的基础上的。可是,结果真的是这样的吗?

Percona 5.5,Percona 5.1,MySQL 5.6关闭sql_mode= ONLY_FULL_GROUP_BY,MySQL5.1等版本下,返回值确实是我们想要的值:按照order by Score desc的顺序,相同Name返回Score值最大的数据;

但是在MySQL5.7,关闭sql_mode= ONLY_FULL_GROUP_BY和mariadb 10.*版本中,相同的Name值,返回则是取了最早写入的数据行,忽略了order by Score desc,按照数据的逻辑存储顺序来返回。其实这是的SQL就等价于select * from tb group by Name;

通过Mysql5.7的官方文档中可以找到答案:

在from 后的子查询中的order by会被忽略

group by cloumn返回的行是无序的

所以第一条SQL请慎重选择。

  • 三条基本类似
  • 这里也不做解释
  • 六、七、八条实现原理基本也类似。
  • 比较特别。因为它的要求是Name可以穷举出来,也不能太多。顺便一提,这里为何用了union all 而不是union。因为不带all的union他会合并重复的记录,union all 不会执行去重操作。这样可以一定程度上提高效率。当然如果有去重需求那就另当别论了。

如果需求是列出每个人分数最高的两条记录呢?

其实以上的7\8\9这三条SQL改变以下参数就可以实现:

select * from tb outTb where exists ( select count(*) from tb where Name = outTb.Name and Score > outTb.Score having count(*) < 2);

select * from tb outTb where 2 > ( select count(*) from tb where Name = outTb.Name and Score > outTb.Score);

( select * from tb where Name = 'Tom' order by Score desc limit 2) union all ( select * from tb where Name = 'Queue' order by Score desc limit 2) union all ( select * from tb where Name = 'Li' order by Score desc limit 2)