如何理解group by,分组还是聚合?

本文解析了SQL中group by的理解误区,强调其并非简单地将表拆分为多个临时表,而是与聚合函数结合使用,提供分组后的聚合结果。通过例题分析了group by在部门工资前三查询中的应用,指出group by更适合翻译为“聚合”。同时,区分了where和having、where和group by的关系,并总结了解题思路中需要注意的问题。

前言

本文并非是对group by的使用说明,而仅是对group by字面理解的一些误区进行解析,以避免在查询时误入歧途。顺便提一下之前写到过的关于排名问题的一篇文章,建议先阅读该文,那么接下来对本文中的题目解答中的一些细节会更容易理解。

例题

Employee 表包含所有员工信息,每个员工有其对应的工号 Id,姓名 Name,工资 Salary 和部门编号 DepartmentId 。

Employee表
+----+-------+--------+--------------+
| Id | Name  | Salary | DepartmentId |
+----+-------+--------+--------------+
| 1  | Joe   | 85000  | 1            |
| 2  | Henry | 80000  | 2            |
| 3  | Sam   | 60000  | 2            |
| 4  | Max   | 90000  | 1            |
| 5  | Janet | 69000  | 1            |
| 6  | Randy | 85000  | 1            |
| 7  | Will  | 70000  | 1            |
+----+-------+--------+--------------+

Department 表包含公司所有部门的信息。

Department表
+----+----------+
| Id | Name     |
+----+----------+
| 1  | IT       |
| 2  | Sales    |
+----+----------+

编写一个 SQL 查询,找出每个部门获得前三高工资的所有员工。例如,根据上述给定的表,查询结果应返回:

+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT         | Max      | 90000  |
| IT         | Randy    | 85000  |
| IT         | Joe      | 85000  |
| IT         | Will     | 70000  |
| Sales      | Henry    | 80000  |
| Sales      | Sam      | 60000  |
+------------+----------+--------+

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/department-top-three-salaries
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

目前对数据库问题,我的解题思维会先进行数据库思维的解题,即所谓不依靠变量直接强上裸逻辑的硬解法,然后再去考虑运用函数思维运用变量+子查询的方式去解题。两种方式没有优劣之分,我个人更倾向于后者,但在sql中代码实现需要一些技巧。但由于本文重点在于通过题目理解group by的用法,所以不解释引入变量的排名解法。

初始思路
  1. group by 对部门分组
  2. 分别应用排名查询语句(排名问题)生成排名列检索各部门工资前三
  3. 筛选排名前三的数据

误区
一方面,要注意group by虽然有分组的作用,但并不是字面理解上将一张表拆分显示为两张临时表的能力,在sql中也不会允许返回两张表这样的语法存在;
另一方面,如果不存储临时表,直接通过聚集函数和Group by组合,在此场景下,实现不了排名的效果。
所以尽管题目中有“每个部门”,思路中有“分组”关键字,但group by并不一定能派上用场。我认为很多地方将其翻译为“聚合”,更符合其用法的本来意图,即为了显示分组后的聚合结果,而非分组细节。

思路改变
  1. 改进排名查询语句,将部门Id添加到计算排名列时的条件语句中
  2. 筛选排名前三的数据

改进建议:
原排名计算方法可以通过添加比较筛选的方式来达到分组排名的效果。

代码实现

select d.Name as Department,a.Name as Employee, Salary 
        from Department d inner join Employee a on d.Id = a.DepartmentId  
        where 3>=
            (select count(distinct b.Salary) from Employee b 
            where a.Salary<=b.Salary and a.DepartmentId=b.DepartmentId) 

疑问

1. where和 group by是否有可替代关系?

答:不是绝对可行的替代关系。因为尽管本文例题中的思路转变只是增加了一个where条件,但实际打破的其实是对排名算法语句的既有印象——算法内部可以再优化来实现更复杂的分组效果,这种结合方式是group by和排名算法结合达不到的效果,但也谈不上是where所独有的特性,而更可能是作者自己没有对where语句掌握的很好的结果。所以,where和group by更应该是互补的关系而不是替代关系。

2. where 和 having 的区别

答:having是对where条件的一些补充,但where是对聚合前表的筛选,having是聚合后结果集的条件筛选。

3. group by究竟是分组还是聚合?

答:从字面翻译看“分组”更为人们所接受,从作用和效果看,group by的结果通常是“分组”然后“聚合”的方式来体现其主要作用,如果只进行分组,结果集完全可以用distinct来代替实现。不过group by也可以只用分组来实现where的功能,比如:通过与in和not in结合,实现根据某字段对整条数据的去重和筛选,只是这种方式有些去简就繁了。所以,就目前我的理解来看,group by基本是和聚合函数绑定使用的,group by被叫做“聚合”更符合应用场景。

总结

以本文为例,可以看出,在分析自己的解题思路时,尽管思路逻辑很顺畅,但实现步骤很有问题。其原因有二:

  1. 数据库中的思路逻辑和语法实现正好是相反的。
  2. 对部分语法的理解浅导致对能否实现某些逻辑存在疑惑。

具体说下这两点问题。
首先,数据库中的思路逻辑和语法实现正好是相反的“先对组筛选再排名再对排名筛选”的先后顺序在语法实现上,对应的是从内到外,“对组筛选”由where子句实现在排名之前,“排名”由select+where子句实现在对排名筛选之前,而“筛选”最后由主句的where子句实现。这种思考方式的好处在于可以在内部操作时就能确定逻辑是否能够完成。而回顾以往的做题经验,更多的可能是从主句开始扩充,但一方面语法掌握不熟练,一方面逻辑上存在混乱,解题效率也就比较低,所以前期可以考虑使用这种“思路先后,实现内外”的方式,对语法熟练后再从主句开始扩充。

其次,关于语法方面,前面讲了很多了,总的来说就是group by的用法目前来看包括两方面:一是和聚集函数的结合,用于解决计数等问题;二是巧用分组后的结果集,与In和not in 结合使用,达到一定的筛选效果。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值