当前位置: 代码网 > it编程>数据库>Mysql > 从多表到子查询详解MySQL中复合查询的核心重点

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

2026年07月16日 Mysql 我要评论
1. 前言在实际开发中,我们经常需要从多张表中提取数据,而不仅仅是单表查询。mysql 的复合查询(多表查询、子查询、合并查询)是面试中的高频考点,也是日常开发必备的技能。本文将基于一个经典的公司管理

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;

小贴士

  • ifnull(comm, 0):如果 comm(奖金)为 null,则用 0 代替。null 不参与运算,任何数据和 null 运算结果都为 null,所以一定要处理。
  • as '年薪':给计算结果起一个别名,方便在 order by 中引用。

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';

小贴士

  • 自连接的本质是将同一张表进行笛卡尔积,然后通过别名区分不同角色。
  • 先找到 ford 员工的领导工号(worker.mgr),再通过 leader.empno 匹配到领导的信息。
  • 对同一张表进行聚合,该表必须取别名,不能直接聚合成为一张表

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 复合查询的所有核心知识点

  • 多表查询:核心是笛卡尔积 + 连接条件,将多表转化为单表。
  • 自连接:通过给表起别名,将一张表视为两张表。
  • 子查询:可以出现在 wherefrom 等子句中,from 子句中的子查询可以作为临时表,是解决复杂问题的利器。
  • 合并查询union 去重,union all 不去重,用于合并多个查询结果。

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

9. 经典面试题与解答

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

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

题目 2wherehaving 的区别是什么?

解答

  • where 用于对分组前的数据进行过滤,不能使用聚合函数。
  • having 用于对分组后的数据进行过滤,可以使用聚合函数(如 avg(), sum(), count() 等)。

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

解答

  • union:合并两个结果集,并自动去除重复行,性能稍差。
  • union 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复合查询的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com