引言:
今天同事翻看之前我写的sql时,问我我这个sql和他写的相比查询效率哪个更好。乍眼一看,竟然没看懂他写的sql,(⊙﹏⊙)b汗。仔细一看,还真是很巧妙,必须要研究研究!
所以便有了本篇内容:mysql如何先查询后分组(求每个分组的 top1)
问题重现:有这样一个需求,需要查询每个分组的某个字段最新(最大)对应的整条记录。举个栗子:假如有个员工表,有id(主键),salary(薪水),depart_id(部门id),求出每个部门薪水最高的员工记录。
实现:
在这之前,我所知道比较简单明了的实现有下面这两种(为了简单,我创建了一个测试表,只包含排序字段和分组字段)
以下是建表语句
- create table sort_group(
- sort int,
- gp int
- )
这里并没有建立索引,具体测试时再对比建立索引时的效率。
第一种实现:
- select a.sort,a.gp from (
- select * from sort_group order by sort desc
- ) a
- group by a.gp
表中数据如下
5.6版本测试结果
5.7版本测试结果
造成如上的原因是5.7版本会把子查询的order by语句优化掉。
第二种实现,利用group_concat()函数
- SELECT
- SUBSTRING_INDEX(
- GROUP_CONCAT(sort ORDER BY sort DESC),
- ',',
- 1
- ),
- gp
- FROM
- sort_group
- GROUP BY
- gp
第三种实现:
- SELECT a.* from sort_group a
- left JOIN sort_group b
- on a.gp = b.gp and a.sort < b.sort
- where b.sort is null
肯定是最大的,这样最后便得到了需求的记录。
效率:
下面测试一下在不建立索引的情况下执行效率。
为了方便模拟数据,本人写了一个存储函数模拟插入数据
- create PROCEDURE random_insert (IN s int,IN g int,IN len int)
- CONTAINS SQL
- Begin
- DECLARE i int;
- set i = 0;
- start transaction;
- while i <= len DO
- insert into sort_group(sort,gp) values(FLOOR(Rand() * s),FLOOR(Rand() * g));
- set i = i + 1;
- end while;
- commit;
- END
先测试每个组中平均有10条数据的情况,为了保证sort不重复,s值尽量大,执行以下语句:
- call random_insert(1000000,10000,100000);
0.105s 0.095s 100+s(汗)
接下啦测试每组中平均有1000条的情况
- call random_insert(1000000,100,100000);
然后我们给两个字段加上索引重复上面两次测试
0.106 0.135s 1000+s
0.101s 0.120s 100+s
从测试结果上看 第三种完全不可用,不难分析原因,第三种产生了笛卡尔积,并且一个组内数量越多,产生的越多。
这里就不通过explain分析查询策略了,结果已经很明显了。
个人建议使用第二种来完成需求。当然这也不是绝对的,需要具体情况具体分析。