SQL分组查询可以使用3种方式实现:
- 相关子查询
- join表连接
- group by
而它们都有各自的适用场景
子查询:
子查询(Sub Query)或者说内查询(Inner Query),也可以称作嵌套查询(Nested Query),是一种嵌套在其他 SQL 查询的 WHERE 子句中的查询
相关子查询:
先说结论——相关子查询会返回【关联外层记录的组信息】,从而到达按组操作的目的
我们知道,每一次执行相关子查询时,都依赖于外查询指针当前指向的记录。这也就是说,每一次相关子查询的执行,都只会查找出和当前外层记录相关联的那一部分(或者说一个相关group)记录。即相关子查询会返回【关联外层记录的组信息】
self join表连接:
用相关子查询实现的分组查询都可以转为通过表self join实现
在相关子查询中,【子查询与外层查询之间相关联的条件】就是【self join的on条件】,这个条件也正是实现分组的依据
因为在相关子查询中,外层每扫描一条记录,内层子查询就将对应组的所有记录查了一遍,也就是外层扫一条记录,内层就扫一个组,这样一个1对多的关系可以用笛卡尔积实现,也就是join了!
group by语句:
group by col用于实现按某个字段分组
应用了group by之后,注意,每个组只会返回【一条记录】,因此,group by 99.99%要和聚合函数(如SUM, COUNT, MAX, AVG等)连用,以返回每一组的统计学信息(如总和、计数、最大值、均值等)
group by col实现分组之后,还可以用having 子句对组别进行进一步的筛选,然后再对筛选后的组别调用聚合函数
那么,相关子查询、self join 和 group by 各自优势在哪里?
先说结论:
- 相关子查询只是在理解上相对group by稍微更困难一点点,特别是在多层嵌套的情况下,但也不是啥问题,它的优势在于可以【实现每个组返回多条记录】,当然每组返回一条也行。但是!!子查询性能非常差,因为MySQL需要对子查询创建临时表,然后用完又得删掉,影响查询效率
- 相关子查询的实现可以等效转写为self join,join的效率比相关子查询高
- group by则【只能实现每组返回一条记录】
- 综上,推荐使用self join和group by,相关子查询尽量不要写
例:
有表Employee:
Id | Name | Salary | DepartmentId |
---|---|---|---|
1 | Joe | 70000 | 1 |
2 | Henry | 80000 | 2 |
3 | Sam | 60000 | 2 |
4 | Max | 90000 | 1 |
5 | Mark | 70000 | 2 |
6 | Fox | 90000 | 1 |
问题1:找出每个部门的最大薪水,以(DepartmentId, Salary)返回
分析:显然,划重点【最大】,说明每个部门只返回一条记录,因此相关子查询和group by都可以用
-- 相关子查询
select distinct e.DepartmentId, e.Salary --加distinct使每组只返回一条记录
from Employee e
where e.Salary =
-- 子查询开始,查找外层记录所在部门的部门最大值
(select max(Salary) from Employee E where E.DepartmentId = e.DepartmentId) ;
################################
-- self join
select distinct e1.DepartmentId, max(e2.Salary)
from Employee e1 join Employee e2
on e1.DepartmentId = e2.DepartmentId -- on后接分组条件
group by e1.Id
################################
-- group by子句 -- 最简单,不解释了
select e.DepartmentId, max(e.Salary)
from Employee e
group by DepartmentId
问题2:找出每个部门拿最大薪水的人,以(Id, DepartmentId, Salary)返回
分析:拿最大薪水的人可以有多个,说明每个部门可能返回多条记录
-- 相关子查询
select e.Id, e.DepartmentId, e.Salary --只需要在上一问的基础上去掉distinct并增加一个Id字段即可
from Employee e
where e.Salary =
(select max(Salary) from Employee E where E.DepartmentId = e.DepartmentId) ;
#########################################
-- self join
select e1.Id, e1.DepartmentId, max(e2.Salary)
from Employee e1 join Employee e2
on e1.DepartmentId = e2.DepartmentId
group by e1.Id
having e1.Salary = max(e2.Salary)
-- 只需在原来基础上加一个having,保证左表的Salary = 本组的最大值
################################
-- group by子句
-- 将问题1的结果作为一个子查询,即先查出部门和最大值,然后在外层做一个嵌套,用in做筛选
select Id, DepartmentId, Salary
from Employee
where (DepartmentId, Salary) in
-- 下面是问题1原封不动挪过来
(select e.DepartmentId, max(e.Salary)
from Employee e
group by DepartmentId);
附:
partition by 和 group by 比较
- 1.group by是【分组函数】,作用在select之前,每组只能返回一条记录,一般与聚合函数连用;
- 2.partition by是【分析函数】,作用在select之后,对每个partition做操作,但不改变select最终返回的记录数量
分组查询之group by进阶
group by是最直观的分组查询写法,通常结合聚合函数就可以获取每组的目标信息(一组返回一条记录)
group by 最简单的用法就是后面跟若干个COLUMNS作为分组的依据,但group by还可以后接一些函数运算来解决一些复杂需求。例如按周分组、按月分组等,就需要对时间做一些计算。mysql提供了DATE_FORMAT函数
event_table记录了事件的开始时间和结束时间
以下sql按周统计了每周的平均事件耗时和事件总量
select
DATE_FORMAT(start_time,'%x-week%v') as time_point,
AVG(TIMESTAMPDIFF(HOUR,start_time,finish_time)) as `avg_time_spent(/hour)`,
COUNT(event_type) as events_count
from event_table
where status = 'Success'
group by DATE_FORMAT(start_time,'%x-week%v')
order by DATE_FORMAT(start_time,'%x-week%v') asc
limit 5000 offset 0

网友评论