Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL复合查询

从多表到子查询详解MySQL中复合查询的核心重点

作者:时间的拾荒人

还在为MySQL的多表查询和子查询头疼吗,本文从基本查询回顾到自连接、合并查询,用实战案例和面试题带你彻底掌握复合查询的核心心法:将多表转化为单表,学会这些,轻松应对开发中的复杂数据提取和SQL面试

1. 前言

在实际开发中,我们经常需要从多张表中提取数据,而不仅仅是单表查询。MySQL 的复合查询(多表查询、子查询、合并查询)是面试中的高频考点,也是日常开发必备的技能。本文将基于一个经典的公司管理系统(EMP、DEPT、SALGRADE 三张表),带你从基础回顾到高级实战,彻底掌握复合查询。

2. 基本查询回顾

在进入多表查询之前,我们先快速回顾一下单表查询的常用技巧,这些是复合查询的基础。

2.1 条件与排序组合

案例:查询工资高于 500 或岗位为 MANAGER 的雇员,同时还要满足他们的姓名首字母为大写的 J。

select * from EMP where (sal > 500 or job = 'MANAGER') and ename like 'J%';

小贴士like 'J%' 用于匹配以 ‘J’ 开头的字符串,% 是通配符,代表任意长度的字符。

案例:按照部门号升序而雇员的工资降序排序。

select * from EMP order by deptno, sal desc;

小贴士order by 后面可以跟多个字段,先按第一个字段排序,如果相同再按第二个字段排序。desc 表示降序,asc 表示升序(默认)。建议显式写出排序方式,避免歧义。

2.2 使用别名与 NULL 处理

案例:使用年薪进行降序排序。

select ename, sal*12+ifnull(comm, 0) as '年薪' from EMP order by 年薪 desc;

小贴士

2.3 聚合与分组

案例:显示工资最高的员工的名字和工作岗位。

select ename, job from EMP where sal = (select max(sal) from EMP);

小贴士:这里用到了子查询,先查出最高工资,再查对应员工。

案例:显示工资高于平均工资的员工信息。

select ename, sal from EMP where sal > (select avg(sal) from EMP);

案例:显示每个部门的平均工资和最高工资。

select deptno, format(avg(sal), 2), max(sal) from EMP group by deptno;

小贴士group by 用于分组,format(avg(sal), 2) 将平均工资格式化为两位小数。

案例:显示平均工资低于 2000 的部门号和它的平均工资。

select deptno, avg(sal) as avg_sal from EMP group by deptno having avg_sal < 2000;

小贴士having 用于对分组后的数据进行过滤,而 where 用于对分组前的数据进行过滤。

案例:显示每种岗位的雇员总数,平均工资。

select job, count(*), format(avg(sal), 2) from EMP group by job;

3. 多表查询

实际开发中,数据往往分散在不同的表中。多表查询的核心思想是将多张表通过关联条件组合成一张临时表,然后进行单表查询。

3.1 笛卡尔积与等值连接

原理:当查询多张表时,如果不加任何条件,MySQL 会将两张表的每一行进行组合,形成笛卡尔积。例如,EMP 表有 14 条记录,DEPT 表有 4 条记录,笛卡尔积就有 14 * 4 = 56 条记录。这通常不是我们想要的,所以需要通过 where 条件过滤出有效数据。

案例:显示雇员名、雇员工资以及所在部门的名字。

select EMP.ename, EMP.sal, DEPT.dname from EMP, DEPT where EMP.deptno = DEPT.deptno;

小贴士EMP.deptno = DEPT.deptno 就是连接条件,它确保了只取部门编号相同的记录,这就是等值连接

3.2 多表查询实战

案例:显示部门号为 10 的部门名、员工名和工资。

select ename, sal, dname from EMP, DEPT where EMP.deptno = DEPT.deptno and DEPT.deptno = 10;

案例:显示各个员工的姓名、工资及工资级别。

select ename, sal, grade from EMP, SALGRADE where EMP.sal between losal and hisal;

小贴士between ... and ... 用于范围匹配,这里将员工的工资与工资等级表进行匹配。

4. 自连接

自连接是指在同一张表内进行连接查询。核心技巧是给表起别名,将其视为两张独立的表

案例:显示员工 FORD 的上级领导的编号和姓名(mgr 是员工领导的编号,对应 empno)。

方法一:子查询

select empno, ename from emp where emp.empno = (select mgr from emp where ename = 'FORD');

方法二:自连接(推荐)

-- 使用到表的别名
--from emp leader, emp worker,给自己的表起别名,因为要先做笛卡尔积,所以别名可以先识别
-- 给表起别名:leader 代表领导表,worker 代表员工表
select leader.empno, leader.ename from emp leader, emp worker where leader.empno = worker.mgr and worker.ename = 'FORD';

小贴士

5. 子查询

子查询是指嵌入在其他 SQL 语句中的 select 语句,也叫嵌套查询。子查询可以出现在 wherefromhaving 等子句中。

5.1 单行子查询

返回一行记录的子查询。

案例:显示 SMITH 同一部门的员工。

select * from EMP WHERE deptno = (select deptno from EMP where ename = 'smith');

5.2 多行子查询

返回多行记录的子查询,常与 inallany 关键字配合使用。

in 关键字:判断列值是否在某一集合中。

案例:查询和 10 号部门的工作岗位相同的雇员的名字、岗位、工资、部门号,但是不包含 10 号部门自己的。

select ename, job, sal, deptno from emp where job in (select distinct job from emp where deptno = 10) and deptno <> 10;

all 关键字:与所有值比较,返回满足所有条件的记录。

案例:显示工资比部门 30 的所有员工的工资都高的员工的姓名、工资和部门号。

select ename, sal, deptno from EMP where sal > all(select sal from EMP where deptno = 30);

any 关键字:与任意值比较,返回满足任一条件的记录。

案例:显示工资比部门 30 的任意员工的工资高的员工的姓名、工资和部门号。

select ename, sal, deptno from EMP where sal > any(select sal from EMP where deptno = 30);

5.3 多列子查询

单行子查询是指子查询只返回单列,单行数据;多行子查询是指返回单列多行数据,都是针对单列而言的,而多列子查询则是指查询返回多个列数据的子查询语句

案例:查询和 SMITH 的部门和岗位完全相同的所有雇员,不含 SMITH 本人。

select ename from EMP where (deptno, job) = (select deptno, job from EMP where ename = 'SMITH') and ename <> 'SMITH';

小贴士(deptno, job) 是一个行构造器,用于同时比较多个列的值。目前全部的子查询,全部都在where子句中,充当判断条件!任何时刻查询出来的临时结构,本质在逻辑上都是表结构。

5.4 在from子句中使用子查询

子查询的结果可以作为一个临时表,放在 from 子句中。这是解决复杂查询的利器。

案例:显示每个高于自己部门平均工资的员工的姓名、部门、工资、平均工资。

select ename, deptno, sal, format(asal, 2) from EMP,
     (select avg(sal) asal, deptno dt from EMP group by deptno) tmp
where EMP.sal > tmp.asal and EMP.deptno = tmp.dt;

 小贴士:将子查询 (select avg(sal) asal, deptno dt from EMP group by deptno) 看作一张临时表 tmp,然后与 EMP 表进行连接查询。

案例:查找每个部门工资最高的人的姓名、工资、部门、最高工资。

select EMP.ename, EMP.sal, EMP.deptno, ms from EMP,
    (select max(sal) ms, deptno from EMP group by deptno) tmp
where EMP.deptno = tmp.deptno and EMP.sal = tmp.ms;

案例:显示每个部门的信息(部门名,编号,地址)和人员数量。

方法一:多表查询

select DEPT.dname, DEPT.deptno, DEPT.loc, count(*) '部门人数' from EMP, DEPT
where EMP.deptno = DEPT.deptno
group by DEPT.deptno, DEPT.dname, DEPT.loc;

方法二:子查询(推荐)

-- 1. 对 EMP 表进行人员统计
select count(*), deptno from EMP group by deptno;
-- 2. 将上面的表看作临时表
select DEPT.deptno, dname, mycnt, loc from DEPT,
    (select count(*) mycnt, deptno from EMP group by deptno) tmp
where DEPT.deptno = tmp.deptno;

核心思想解决多表问题的本质是:想办法将多表转化成为单表。 所以 MySQL 中,所有 select 的问题全部都可以转化为单表问题。

6. 合并查询

在实际应用中,为了合并多个 select 的执行结果,可以使用集合操作符 unionunion all

6.1union:去重合并

该操作符用于取得两个结果集的并集,并自动去掉重复行

案例:将工资大于 2500 或职位是 MANAGER 的人找出来。

select ename, sal, job from EMP where sal > 2500
union
select ename, sal, job from EMP where job = 'MANAGER';

结果

+-------+---------+-----------+
| ename | sal     | job       |
+-------+---------+-----------+
| JONES | 2975.00 | MANAGER   |
| BLAKE | 2850.00 | MANAGER   |
| SCOTT | 3000.00 | ANALYST   |
| KING  | 5000.00 | PRESIDENT |
| FORD  | 3000.00 | ANALYST   |
| CLARK | 2450.00 | MANAGER   |
+-------+---------+-----------+

小贴士:可以看到,JONES、BLAKE、CLARK 虽然同时满足两个条件,但在结果中只出现一次,因为 union 去重了。

6.2union all:不去重合并

该操作符用于取得两个结果集的并集,但不会去掉重复行

案例:将工资大于 2500 或职位是 MANAGER 的人找出来。

select ename, sal, job from EMP where sal > 2500
union all
select ename, sal, job from EMP where job = 'MANAGER';

结果

+-------+---------+-----------+
| ename | sal     | job       |
+-------+---------+-----------+
| JONES | 2975.00 | MANAGER   |
| BLAKE | 2850.00 | MANAGER   |
| SCOTT | 3000.00 | ANALYST   |
| KING  | 5000.00 | PRESIDENT |
| FORD  | 3000.00 | ANALYST   |
| JONES | 2975.00 | MANAGER   |
| BLAKE | 2850.00 | MANAGER   |
| CLARK | 2450.00 | MANAGER   |
+-------+---------+-----------+

小贴士:JONES 和 BLAKE 出现了两次,因为 union all 只是简单地将结果拼接在一起。

7. 实战 OJ

这里是一些经典的 SQL 面试题,来自牛客网等平台,帮助你巩固所学知识。

题目 1:查找所有员工入职时候的薪水情况,给出 emp_no 以及 salary,并按照 emp_no 进行逆序。

-- 假设有 employees 表和 salaries 表
select e.emp_no, s.salary
from employees e
join salaries s on e.emp_no = s.emp_no
where s.from_date = e.hire_date
order by e.emp_no desc;

题目 2:针对库中的所有表生成 select count(*) from tableName 对应的 SQL 语句。

-- 使用 information_schema 获取所有表名
select concat('select count(*) from ', table_name, ';') as 'SQL语句'
from information_schema.tables
where table_schema = 'your_database_name';

题目 3:获取所有非 manager 的员工 emp_no

-- 假设有 employees 表和 dept_manager 表
select emp_no from employees
where emp_no not in (select emp_no from dept_manager);

题目 4:获取所有员工当前的 manager,如果当前的 manager 是自己的话结果不显示,当前表示 to_date='9999-01-01'

-- 假设有 dept_emp 表和 dept_manager 表
select de.emp_no, dm.emp_no as manager_no
from dept_emp de
join dept_manager dm on de.dept_no = dm.dept_no
where dm.to_date = '9999-01-01'
  and de.emp_no <> dm.emp_no;

8. 总结

本文从基本查询回顾开始,逐步深入到多表查询、自连接、子查询和合并查询,涵盖了 MySQL 复合查询的所有核心知识点

核心心法解决多表问题的本质是:想办法将多表转化成为单表。 所以 MySQL 中,所有 select 的问题全部都可以转化为单表问题。

9. 经典面试题与解答

题目 1:什么是笛卡尔积?如何避免?

解答:笛卡尔积是指在没有连接条件的情况下,将两张表的每一行进行组合,结果集的行数是两张表行数的乘积。避免笛卡尔积的方法是在 where 子句中添加有效的连接条件(如等值连接)。

题目 2wherehaving 的区别是什么?

解答

题目 3unionunion all 的区别是什么?

解答

题目 4:什么是子查询?子查询可以出现在哪些子句中?

解答:子查询是嵌套在其他 SQL 语句中的 select 语句。它可以出现在 wherefromhavingselect 等子句中。其中,from 子句中的子查询被当作临时表使用,是解决复杂查询的常用技巧。

题目 5:请用一条 SQL 语句查询出每个部门工资最高的员工信息。

解答

select e.*
from emp e
join (select deptno, max(sal) as max_sal from emp group by deptno) tmp
on e.deptno = tmp.deptno and e.sal = tmp.max_sal;

以上就是从多表到子查询详解MySQL中复合查询的核心重点的详细内容,更多关于MySQL复合查询的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文