MySQL 复合查询全解:多表关联、自连接、子查询与 Union 实战

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》

《Linux系统从入门到实践》《Linux网络从入门到实践》

《Qt 方寸极境》 《MySQL》

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言

一、基础查询回顾

二、多表查询:跨数据表关联查询核心用法

[2.1 测试表结构](#2.1 测试表结构)

[2.2 实战演示案例(附详细图示)](#2.2 实战演示案例(附详细图示))

[2.2.1 双表关联:整合员工与部门数据](#2.2.1 双表关联:整合员工与部门数据)

[2.2.2 多表联动 + 条件过滤:筛选指定部门员工信息](#2.2.2 多表联动 + 条件过滤:筛选指定部门员工信息)

[2.2.3 关联多张表:员工信息搭配薪资等级](#2.2.3 关联多张表:员工信息搭配薪资等级)

[2.3 多表查询避坑点](#2.3 多表查询避坑点)

三、自连接:单张数据表内部关联查询

[3.1 自连接语法解析](#3.1 自连接语法解析)

[3.2 实战示例:查找员工对应的直属上级(附详细图示)](#3.2 实战示例:查找员工对应的直属上级(附详细图示))

[3.3 使用自连接的实用技巧](#3.3 使用自连接的实用技巧)

四、子查询:嵌套查询多样化实践

[4.1 单行子查询:返回单行单列结果集](#4.1 单行子查询:返回单行单列结果集)

[4.2 多行子查询:返回多行单列结果集](#4.2 多行子查询:返回多行单列结果集)

[4.2.1 in 关键字:匹配集合内任意数值](#4.2.1 in 关键字:匹配集合内任意数值)

[4.2.2 all 关键字:对比集合全部数据](#4.2.2 all 关键字:对比集合全部数据)

[4.2.3 any 关键字:匹配集合任一满足条件数据](#4.2.3 any 关键字:匹配集合任一满足条件数据)

[4.2.4 关键字 in VS 关键字 any 区别(补充)](#4.2.4 关键字 in VS 关键字 any 区别(补充))

[4.3 多列子查询:返回多行多列数据集](#4.3 多列子查询:返回多行多列数据集)

[4.4 FROM 后嵌入子查询:充当临时数据表(重点)](#4.4 FROM 后嵌入子查询:充当临时数据表(重点))

[4.5 子查询避坑指南](#4.5 子查询避坑指南)

[五、合并查询:union 与 union all 使用详解](#五、合并查询:union 与 union all 使用详解)

[5.1 核心语法](#5.1 核心语法)

[5.2 实战案例](#5.2 实战案例)

[六. 实战 OJ 真题:复合查询综合落地练习](#六. 实战 OJ 真题:复合查询综合落地练习)

七、复合查询总结

结束语


前言

日常业务开发场景里,仅依靠单表查询很难支撑复杂的数据检索需求。业务数据通常分库分表存储,例如员工信息、部门信息、薪资等级分散在不同数据表,想要整合完整数据就离不开跨表关联;进行条件筛选与数据统计时,经常需要嵌套查询构建过滤逻辑;面对多组结果集整合,还需要使用集合操作语法。本文系统讲解 MySQL 复合查询各类核心用法,覆盖多表联查、自连接、子查询以及合并查询。文中全部 SQL 语句统一采用小写书写,贴近工程开发规范,搭配大量实操示例,同时梳理开发过程中常见误区与优化建议。

一、基础查询回顾

在学习复杂复合查询前,先回顾基础查询的核心语法,为后续进阶打基础:

sql 复制代码
-- 1. 条件筛选:工资>500或岗位为manager,且姓名首字母为J
select * from emp where (sal>500 or job='manager') and ename like 'j%'
sql 复制代码
-- 2. 多字段排序:部门号升序,工资降序
select * from emp order by deptno asc, sal desc;
sql 复制代码
-- 3. 计算字段+排序:年薪(sal*12+补贴)降序
select ename, sal*12+ifnull(comm,0) as 年薪 from emp order by 年薪 desc;
sql 复制代码
-- 4. 显示工资最高的员工的名字和工作岗位
select ename, job from emp where sal = (select max(sal) from emp);
sql 复制代码
-- 5. 显示工资高于平均工资的员工信息
select ename, sal from emp where sal > (select avg(sal) from emp);
sql 复制代码
-- 6. 显示每个部门的平均工资和最高工资
select deptno, format(avg(sal),2) as 平均工资, max(sal) as 最高工资 from emp group by deptno;
sql 复制代码
-- 7. 显示平均工资低于2000的部门号和它的平均工资
select deptno, avg(sal) as avg_sal from emp group by deptno having avg_sal < 2000;
sql 复制代码
-- 8. 显示每种岗位的雇员总数,平均工资
select job, count(*) as 雇员总数, format(avg(sal),2) as 平均工资 from emp group by job;

二、多表查询:跨数据表关联查询核心用法

多表查询是复合查询的基础,用于从多个关联表中提取数据,核心是通过 "关联字段" 消除笛卡尔积(无关联条件时,表 1 所有行与表 2 所有行组合,数据量爆炸)。

2.1 测试表结构

本次实战基于 3 张经典表,先明确表结构和关联关系:

  • emp(员工表) :存储员工基本信息,关联字段deptno(关联部门表)、sal(关联薪资等级表);
  • dept(部门表) :存储部门信息,关联字段deptno
  • salgrade(薪资等级表) :存储薪资等级规则,关联字段losal(最低工资)、hisal(最高工资)。

2.2 实战演示案例(附详细图示)

接下来我们会例举出非常多的示例来帮助大家建立对多表查询的认识,并且在图中我会为大家详细讲解为什么需要多表查询以及怎么实现多表查询。

2.2.1 双表关联:整合员工与部门数据

需求:查询员工姓名、工资及所在部门名称

代码演示:

sql 复制代码
-- 核心:通过deptno关联emp和dept表,消除笛卡尔积
select emp.ename, emp.sal, dept.dname 
from emp, dept 
where emp.deptno = dept.deptno;

图示分析:

2.2.2 多表联动 + 条件过滤:筛选指定部门员工信息

需求:查询 10 号部门的员工姓名、工资及部门名称

代码演示:

sql 复制代码
select emp.ename, emp.sal, dept.dname 
from emp, dept 
where emp.deptno = dept.deptno 
  and dept.deptno = 10; -- 筛选10号部门

图示分析:

2.2.3 关联多张表:员工信息搭配薪资等级

需求:查询员工姓名、工资及对应的薪资等级

代码演示:

sql 复制代码
select emp.ename, emp.sal, salgrade.grade 
from emp, salgrade 
where emp.sal between salgrade.losal and salgrade.hisal; -- 工资在薪资等级区间内

图示分析:

2.3 多表查询避坑点

  • 必须加关联条件:无关联条件会产生笛卡尔积(如 emp 有 14 行、dept 有 4 行,会产生 14×4=56 行无效数据);
  • 字段歧义需加表名前缀 :若多个表有同名字段(如deptno),需用表名.字段区分;
  • 关联字段类型必须一致:emp.deptno 和 dept.deptno 需同为 int 类型,否则关联失效。

三、自连接:单张数据表内部关联查询

自连接 是多表查询的特殊形式 ------ 将同一张表当作两张表使用 ,通过别名区分,适用于查询表内关联数据(如员工与上级领导的关系)。

3.1 自连接语法解析

sql 复制代码
select 表别名1.字段, 表别名2.字段 
from 表 表别名1, 表 表别名2 
where 表别名1.关联字段 = 表别名2.关联字段 [and 筛选条件];

3.2 实战示例:查找员工对应的直属上级(附详细图示)

需求:查询员工 ford 的上级领导编号和姓名(emp 表中mgr字段是领导的empno

sql 复制代码
-- 方法1:子查询(简单场景)
select empno, ename from emp 
where empno = (select mgr from emp where ename='ford');
sql 复制代码
-- 方法2:自连接(复杂场景更灵活)
select leader.empno as 领导编号, leader.ename as 领导姓名 
from emp leader, emp worker -- leader=领导表,worker=员工表
where leader.empno = worker.mgr -- 领导编号=员工的上级编号
  and worker.ename = 'ford'; -- 筛选员工为ford

3.3 使用自连接的实用技巧

  • 必须给表起不同别名(如leaderworker),否则 MySQL 无法区分两张 "虚拟表";
  • 关联字段需是表内的关联关系(如员工表的mgr与自身的empno)。

四、子查询:嵌套查询多样化实践

子查询(嵌套查询)是指嵌入在其他 SQL 语句中的 select 语句,按返回结果可分为单行、多行、多列子查询,按位置可分为 where 子句、from 子句中的子查询。

4.1 单行子查询:返回单行单列结果集

适用于筛选条件为 "等于、大于、小于" 单个值的场景,常用比较运算符(=、>、<、>=、<=)。

实战案例:

需求:查询与 smith 同部门的所有员工(不含 smith)

代码演示:

sql 复制代码
select * from emp 
where deptno = (select deptno from emp where ename='smith') -- 子查询返回smith的部门号
  and ename != 'smith'; -- 排除smith本人

图示分析:

4.2 多行子查询:返回多行单列结果集

适用于筛选条件为 "在多个值中""大于所有值""大于任意值" 的场景,常用关键字in、all、any。

4.2.1 in 关键字:匹配集合内任意数值

实战案例:

需求:查询和 10 号部门岗位相同,但不属于 10 号部门的员工

代码演示:

sql 复制代码
select ename, job, sal, deptno from emp 
where job in (select distinct job from emp where deptno=10) -- 子查询返回10号部门的所有岗位
  and deptno != 10; -- 排除10号部门

图示分析:

4.2.2 all 关键字:对比集合全部数据

实战案例:

需求:查询工资比 30 号部门所有员工工资都高的员工

代码演示:

sql 复制代码
select ename, sal, deptno from emp 
where sal > all(select sal from emp where deptno=30); -- 工资>30号部门所有员工工资

图示分析:

4.2.3 any 关键字:匹配集合任一满足条件数据

实战案例:

需求:查询工资比 30 号部门任意员工工资高的员工(含自身部门)

代码演示:

sql 复制代码
select ename, sal, deptno from emp 
where sal > any(select sal from emp where deptno=30); -- 工资>30号部门至少一个员工工资

图示分析:

4.2.4 关键字 in VS 关键字 any 区别(补充)

4.3 多列子查询:返回多行多列数据集

适用于筛选条件需匹配 "多个字段组合" 的场景,子查询返回多列,主查询用括号接收字段组合。

实战案例:

需求:查询与 smith 部门和岗位完全相同的员工(不含 smith)

代码演示:

sql 复制代码
select ename from emp 
where (deptno, job) = (select deptno, job from emp where ename='smith') -- 匹配部门+岗位组合
  and ename != 'smith';

图示分析:

4.4 FROM 后嵌入子查询:充当临时数据表(重点)

将子查询结果当作 "临时表",用于复杂统计分析(如先聚合再关联),核心是给临时表起别名。

实战案例 1:

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

代码演示:

sql 复制代码
select emp.ename, emp.deptno, emp.sal, format(tmp.asal,2) as 部门平均工资 
from emp, 
     (select avg(sal) as asal, deptno as dt from emp group by deptno) tmp -- 临时表:各部门平均工资
where emp.deptno = tmp.dt -- 员工部门=临时表部门
  and emp.sal > tmp.asal; -- 员工工资>部门平均工资

图示分析:

实战案例 1 拓展练习(显示这些员工所对应的部门名称):

实战案例 2:

查找每个部门工资最高的人的姓名、工资、部门、最高工资

代码演示:

sql 复制代码
select emp.ename, emp.sal, emp.deptno, tmp.ms as 部门最高工资 
from emp, 
     (select max(sal) as ms, deptno from emp group by deptno) tmp -- 临时表:各部门最高工资
where emp.deptno = tmp.deptno 
  and emp.sal = tmp.ms;

图示分析:

实战案例 3:

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

代码演示:

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

图示分析:

4.5 子查询避坑指南

  • 单行子查询只能用单行运算符 :若子查询返回多行,不能用=,需用in
  • from 子句的子查询必须起别名:MySQL 要求临时表必须有别名,否则报错;
  • 子查询尽量简化:复杂子查询可拆分为临时表或多步查询,提升可读性和性能。

五、合并查询:union 与 union all 使用详解

合并查询用于将多个 select 语句的结果集合并为一个 ,适用于多条件独立查询后合并结果的场景,核心是union(去重)union all(不去重)

5.1 核心语法

sql 复制代码
-- 去重合并(自动删除重复行)
select 字段 from 表1 where 条件
union
select 字段 from 表2 where 条件;

-- 不去重合并(保留重复行,性能更优)
select 字段 from 表1 where 条件
union all
select 字段 from 表2 where 条件;

5.2 实战案例

案例 1:union 去重合并

查询工资 > 2500 或岗位为 manager 的员工(去重)

代码演示:

sql 复制代码
select ename, sal, job from emp where sal>2500
union -- 自动去重(manager中工资>2500的员工只显示一次)
select ename, sal, job from emp where job='manager';
sql 复制代码
+-------+---------+-----------+
| 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   |
+-------+---------+-----------+

案例 2:union all 不去重合并

查询工资 > 2500 或岗位为 manager 的员工(保留重复)

代码演示:

sql 复制代码
select ename, sal, job from emp where sal>2500
union all -- 保留重复行(manager中工资>2500的员可能会显示多次)
select ename, sal, job from emp where job='manager';
sql 复制代码
+-------+---------+-----------+
| 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   |
+-------+---------+-----------+

六. 实战 OJ 真题:复合查询综合落地练习

真题 1:查找所有员工入职时的薪水情况(emp_no+salary,逆序)
查找所有员工入职时候的薪水情况_牛客题霸_牛客网

sql 复制代码
select employees.emp_no, salary 
from employees, salaries 
where employees.emp_no=salaries.emp_no 
and hire_date=from_date 
order by employees.emp_no desc;

真题 2:获取所有非 manager 的员工 emp_no
获取所有非manager的员工emp_no_牛客题霸_牛客网

sql 复制代码
select emp_no 
from employees 
where emp_no not in(select emp_no from dept_manager);

真题 3:获取所有员工当前的 manager(排除 manager 是自己的情况)

获取所有员工当前的manager_牛客题霸_牛客网

sql 复制代码
select dept_emp.emp_no, dept_manager.emp_no as manager 
from dept_emp, dept_manager 
where dept_emp.dept_no=dept_manager.dept_no 
and dept_emp.emp_no<>dept_manager.emp_no;

七、复合查询总结

MySQL 复合查询是解决复杂业务需求的核心,核心要点总结:

  • 多表查询:通过关联字段消除笛卡尔积,适用于跨表提取数据;
  • 自连接:同表当作两张表,适用于表内关联(如员工与领导);
  • 子查询:嵌套在 where/from 子句中,灵活筛选和统计,需注意单行 / 多行匹配规则;
  • 合并查询:union(去重)和 union all(不去重),适用于多结果集合并;
  • 避坑关键:关联字段一致、临时表起别名、优先选择高效语法(如 union all 替代 union)。

结束语

本篇系统梳理了 MySQL 复合查询各类实现方式,涵盖多表联查、自连接、子查询与合并查询等核心语法。不同查询方案各有适用场景,开发中不能一味套用模板,需要结合表数据量、索引情况合理选择写法。

熟练掌握复合查询是编写高级 SQL 的基础,后续遇到复杂业务统计、多维度数据检索需求时,便能灵活组合各类查询语法。同时需要留意文中提到的性能隐患,避免写出低效 SQL。大家可以结合配套练习题动手实操,加深理解。

相关推荐
g10565591399 小时前
LAMP博客平台Wordpress实战
android·mysql·nginx·php
我不是疯子是傻子10 小时前
Qt CAN通信周期发送抖动?实测定时器精度校准与时间戳补偿方案
开发语言·数据库·qt
这个DBA有点耶10 小时前
数据库迁移怎么做到“零翻车“:金仓 KDMS/KDTS/KFS 工具链实测
数据库·架构·dba
2501_9378609411 小时前
MySQL联合查询(多表查询)
数据库·mysql
名字还没想好☜11 小时前
Next.js 用 Server Components 直连数据库:去掉 API 层的边界,和三条别踩的安全红线
前端·javascript·数据库·安全·react·next.js
迷迭香yy12 小时前
行业板块轮动因子实战从板块资金到因子建模的本地化Python全流程
数据库·人工智能·python
SelectDB12 小时前
抖音集团 实时数据仓库:Apache Doris / SelectDB 的技术能力与实践
数据库
SelectDB13 小时前
网易 日志与时序数据分析:Apache Doris / SelectDB 的技术能力与实践
数据库
weixin_5394467813 小时前
Navicat Premium 17报缺少ODBC驱动
数据库
这个DBA有点耶13 小时前
迁移中的数据一致性挑战:如何确保百万行数据“搬得对”
mysql·架构·dba