SQL中的分组查询之我见 2020-04-07(未经允许,禁止转载)

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

你可能感兴趣的:(SQL中的分组查询之我见 2020-04-07(未经允许,禁止转载))