# 图解面试题：如何分组比较？

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5nb3SMtqgIlDKpgv9%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_1.png?alt=media\&token=f6f12105-0815-48b2-a9d3-ef98d1e3c796)

**【题目】**

现在有三个表，“学生表”，“课程表”，“成绩表”。

“学生表” 记录了学生的基本信息，有 “学号”、“姓名”、“出生日期”、“性别”。

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5ng-661MZIVc4PqwN%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_2.jpg?alt=media\&token=dacdae44-70e6-4efe-bc97-2614e673c34e)

“成绩表”记录了学生选修课程的成绩，包括 “学号”，选修的“课程号” 以及对应课程的“成绩”。

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5nikngLj706MJynXB%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_3.jpg?alt=media\&token=0ded4e50-8ac7-4ded-bc6e-716d9c17972d)

“课程表” 记录了学生选修的课程信息，包括课程号、课程及其对应的 “老师号”

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5np6MbzgtVMQ1TPyS%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_4.jpg?alt=media\&token=96f86f7b-1618-49fd-91d7-94f75ac699bd)

“学生表”和 “成绩表” 通过 “学号” 联结，“成绩表”和 “课程表” 通过 “课程编号” 联结。

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5ntXT5Przr3Sjj1gK%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_5.jpg?alt=media\&token=aaa09224-1d4d-4f07-89f1-5e51f2afeeb3)

现在要查找出每门课程中成绩最好的学生的姓名和该学生的课程及成绩。

需要注意：可能出现并列第一的情况。

**【解题思路】**

**1. 确定查询结果**

题目要求每门课程中成绩最好的学生的姓名和该学生的课程及成绩。可以知道查询结果是：

> select 姓名, 课程名称, 成绩

查询结果的 “姓名” 在“学生表”里，“课程名称”在 “课程表” 里，“成绩”在 “成绩表” 里。这里涉及到了 3 个表，所以需要进行多表查询。

**2. 如何进行多表查询？**

涉及到多表查询，在之前的课程[《从零学会 sql：多表查询》](http://mp.weixin.qq.com/s?__biz=MzAxMTMwNTMxMQ==\&mid=2649247566\&idx=2\&sn=5af748b677eb72028764dde0577675fb\&chksm=835fc77eb4284e68e8cfe3f08c5a671b9e080b2651f20b40b1c793ffda4042ae43ad8f35a755\&scene=21#wechat_redirect)里讲过需要用到多表联结。

涉及到多表联结，要考虑清楚两个问题：哪种联结类型？如何联结？

**1）哪种联结类型？**

多表的联结又分为以下几种类型：

> 1）左联结（left join），联结结果保留左表的全部数据
>
> 2）右联结（right join），联结结果保留右表的全部数据
>
> 3）内联结（inner join），取两表的公共数据

其中 “成绩表” 作为 3 个表联结的中间桥梁，所以要以 “成绩表” 表进行左联结，保留左边表（成绩表）里的全部数据。

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5nx-5F4KXy-biVJcM%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_6.jpg?alt=media\&token=1ca39cf2-5bab-425a-b34e-86f9cafff8f0)

**2）如何联结？**

“学生信息表”和 “成绩表” 都有“学号”，所以联结条件为学号。

on a. 学号 = c. 学号

“课程表”和 “成绩表” 都有“课程号”，所以联结条件为课程号。

on a. 课程号 = b. 课程号

多表联结的 sql 如下：

```sql
from 成绩表 a
left join 课程表 b on a.课程号=b.课程号
left join 学生信息表 c on a.学号=c.学号
```

3）多表联结最终 sql

结合前面两步的 sql 如下：

```sql
select c.学号,c.姓名,b.课程,a.成绩
from 成绩表 a
left join 课程表 b on a.课程号=b.课程号
left join 学生信息表 c on a.学号=c.学号
```

运行结果如下：

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5o0rk8xTR-Jyzj2bC%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_7.jpg?alt=media\&token=a9fecca5-005c-4554-88b5-bdc8014976e9)

**3. 确定查询条件**

题目要求的查询条件是：**每门课程**中**成绩最高**的学生。

我们来看这句话里的关键词：

> 1）“每门课程”，每当出现 “每” 就是要用分组汇总了，所以是子句（group by 课程号）
>
> 2）“成绩最好” ，就是最大成绩了，所以是 max(成绩)

因此，查询条件是：

```sql
select 课程号,max(成绩) 
from 成绩表 
group by 课程号;
```

**4. 合并前面的步骤，确定最终查询**

将第 3 步的查询条件，放到第 2 步多表联结的结果中用于筛选出符合条件的数据。所以查询条件如下：

```sql
where (课程号,成绩) in
(select 课程号,max(成绩) 
from 成绩表 
group by 课程号);
```

需要注意的是，当两列同时作为关键字段进行条件查询时，比如这个案例里是 (课程号, 成绩) in，是将两列合成一个值来查找。比如，“语文” 和“90”合并为值“语文 90”。

所以这两列的顺序要和子查询里列的顺序保持一致。如果列的段顺序不一样，比如 “**90 语文**” 和 “**语文 90**” 就匹配不上，那么查询结果就是空的了。

最终 sql：

```sql
select c.学号,c.姓名,b.课程,a.成绩
from 成绩表 a
left join 课程表 b on a.课程号=b.课程号
left join 学生表 c on a.学号=c.学号
where (a.课程号,a.成绩) in
(select 课程号,max(成绩) from 成绩表 group by 课程号);
```

**【本题考点】**

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5o6AYiE-oXJaHZvEY%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_8.png?alt=media\&token=02cb6a6b-b81d-45fe-9088-2ef6f80101fc)

1）考察三个表的联结，遇到多表查询要想起这个图。

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5oADF3QevOWjYEkKo%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_9.jpg?alt=media\&token=66c51bf6-4e03-42b5-992e-84e7ba12b12f)

2）考察子查询。当一个查询是另一个查询的条件时则需要用到子查询。

3）考察分组汇总，当题目中出现 “每个” 这样的词，要想起用分组汇总来实现。

4）考察逻辑思维能力，如何将题目复杂的表达拆解成一个一个子问题，这方面可以学习[《猴子 分析方法》](http://mp.weixin.qq.com/s?__biz=MzAxMTMwNTMxMQ==\&mid=2649246563\&idx=2\&sn=3ffe509999d144d23dec5acc101fc2ef\&chksm=835fc353b4284a45ce01391453fe2fec1b225bbd6bbdb67dd7f304aacdd4f21f60d0b27ba309\&scene=21#wechat_redirect)课程里的 “逻辑树分析方法” 来提高。

5）这类型题目属于在 “每个组里比较”，还可以使用“关联子查询” 来做，

**【举一反三】**

有两个表，Employee 表用于记录员工的薪水和在哪个部门，包括员工的 Id、员工的姓名（Name）、薪水（Salary） 和 员工所在部门 Id（Department Id）。

Department 表用于记录公司所有部门的信息，包括部门 Id，部门名称（Name）。

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5oJ-KIJYjdVtgIiFm%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_10.png?alt=media\&token=52560591-7db7-4cd4-80ee-3777f7514e0f)

找出每个部门工资最高的员工。例如，根据上述给定的表格，员工（Max）在 IT 部门有最高工资，Henry 在 Sales 部门有最高工资。

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5oN5pxDCJfukMs3nM%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_11.png?alt=media\&token=9dafcf7c-80a1-42af-902b-1e7236d1cdc5)

**思路：**

查询结果包括了两个表的列名，所以要进行多表联结。

1）两表联结

如何联结？

因为要查的是所有员工，所以是以员工表（表名 Employee）进行左联结。

联结条件是什么？通过部门 Id 联结。

所以多表联结的 sql 如下：

```sql
from Employee
left join Department on Employee.DepartmentId = Department.Id
```

2）找出每个部门内最高的工资作为子查询从而确定查询的条件

```sql
where (Employee.DepartmentId,Salary) in
(select DepartmentId,max(Salary) 
from Employee 
group by DepartmentId)
```

3）最终 sql 如下

```sql
select Department.name,Employee.name,Salary
from Employee
left join Department on Employee.DepartmentId = Department.Id
where (Employee.DepartmentId,Salary) in
(select DepartmentId,max(Salary) 
from Employee 
group by DepartmentId);
```

留道思考题，这类型题目属于在 “每个组里比较”，在之前的课程[《猴子 从零学会 sql》](http://mp.weixin.qq.com/s?__biz=MzAxMTMwNTMxMQ==\&mid=2649247566\&idx=2\&sn=5af748b677eb72028764dde0577675fb\&chksm=835fc77eb4284e68e8cfe3f08c5a671b9e080b2651f20b40b1c793ffda4042ae43ad8f35a755\&scene=21#wechat_redirect)里讲过，还可以使用 “关联子查询” 来做，你会吗？

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5oSTY3iFliPD3glRm%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_12.jpg?alt=media\&token=b61bf87c-3768-4a03-9ef4-5b85f12e1c7d)

推荐：[如何从零学会 sql？](http://mp.weixin.qq.com/s?__biz=MzAxMTMwNTMxMQ==\&mid=2649247566\&idx=2\&sn=5af748b677eb72028764dde0577675fb\&chksm=835fc77eb4284e68e8cfe3f08c5a671b9e080b2651f20b40b1c793ffda4042ae43ad8f35a755\&scene=21#wechat_redirect)

![](https://4109408004-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MTuZ3PxkXf2a7UbRybW%2F-MV5nRXLnCyX5bd5_r6k%2F-MV5oXL-dl5PjesCXbUX%2F%E5%A6%82%E4%BD%95%E5%88%86%E7%BB%84%E6%AF%94%E8%BE%83_13.jpg?alt=media\&token=50996d4e-6c6e-455a-903e-72173b0d6bb4)
