目录
[1. 单表查询回顾:夯实基础操作](#1. 单表查询回顾:夯实基础操作)
[1.1 多条件筛选查询](#1.1 多条件筛选查询)
[1.2 自定义排序查询](#1.2 自定义排序查询)
[1.3 聚合与筛选结合查询](#1.3 聚合与筛选结合查询)
[2. 多表查询:关联多张表取数](#2. 多表查询:关联多张表取数)
[2.1 两表关联查询](#2.1 两表关联查询)
[2.2 三表关联查询](#2.2 三表关联查询)
[3. 自连接:同一张表的 "自我关联"](#3. 自连接:同一张表的 “自我关联”)
[4. 子查询:嵌套查询的灵活应用](#4. 子查询:嵌套查询的灵活应用)
[4.1 单行子查询(返回 1 行结果)](#4.1 单行子查询(返回 1 行结果))
[4.2 多行子查询(返回多行结果)](#4.2 多行子查询(返回多行结果))
[4.3 子查询嵌入 from 子句](#4.3 子查询嵌入 from 子句)
[5. 合并查询:union 与 union all](#5. 合并查询:union 与 union all)
在 MySQL 日常使用中,单表查询仅能满足基础数据需求,而实际开发中,数据往往分散在多张表中,且需要复杂的条件筛选与统计。本文将从单表查询回顾入手,逐步深入多表查询、自连接、子查询等复合场景,结合真实案例拆解用法,帮你掌握企业级查询技巧。
1. 单表查询回顾:夯实基础操作
单表查询是复合查询的基石,**核心围绕「筛选条件」「排序规则」「聚合统计」**三大维度展开,以下通过经典案例复习关键用法。
1.1 多条件筛选查询
需求:查询工资高于 3000 或 岗位为「ANALYST」的雇员,且姓名首字母为大写「S」。实现方式有两种:通配符匹配或字符串截取函数,结果一致但适用场景不同。
- 方式 1:使用like通配符(更简洁,适合模糊匹配场景)
            
            
              cpp
              
              
            
          
          select * from emp 
where (sal > 3000 or job = 'ANALYST') 
  and ename like 'S%';- 方式 2:使用**substring**函数(更精准,适合固定位置匹配场景)
            
            
              cpp
              
              
            
          
          select * from emp 
where (sal > 3000 or job = 'ANALYST') 
  and substring(ename, 1, 1) = 'S';查询结果(示例):
            
            
              bash
              
              
            
          
          +--------+-------+---------+------+---------------------+---------+------+--------+
| empno  | ename | job     | mgr  | hiredate            | sal     | comm | deptno |
+--------+-------+---------+------+---------------------+---------+------+--------+
| 007788 | SCOTT | ANALYST | 7566 | 1987-04-19 00:00:00 | 3000.00 | NULL |     20 |
+--------+-------+---------+------+---------------------+---------+------+--------+
1 row in set (0.00 sec)1.2 自定义排序查询
排序不仅支持表中原有字段,还能基于「计算字段」排序,比如按「年薪」排序。需注意:奖金comm可能为NULL,需用ifnull函数处理空值,避免计算结果异常。
- 场景 1:按「月薪 ×12」计算年薪排序
            
            
              cpp
              
              
            
          
          select ename, sal*12 as 年薪 
from emp 
order by 年薪 desc;- 场景 2:按「月薪 ×12 + 奖金」计算年薪排序(处理空值)
            
            
              cpp
              
              
            
          
          select ename, sal*12 + ifnull(comm, 0) as 年薪 
from emp 
order by 年薪 desc;1.3 聚合与筛选结合查询
聚合查询(avg/max/count等) 需搭配group by 分组,若需过滤聚合结果,需用having(区别于where过滤行数据)。
- 场景 1:查询每个部门的平均工资和最高工资
            
            
              cpp
              
              
            
          
          select deptno, avg(sal) as 平均工资, max(sal) as 最高工资 
from emp 
group by deptno;- 场景 2:查询平均工资低于 2500 的部门(聚合后筛选)
            
            
              cpp
              
              
            
          
          select deptno, avg(sal) as 平均工资 
from emp 
group by deptno 
having 平均工资 < 2500;查询结果(示例):
            
            
              cpp
              
              
            
          
          +--------+-------------+
| deptno | 平均工资    |
+--------+-------------+
|     20 | 2175.000000 |
|     30 | 1566.666667 |
+--------+-------------+
2 rows in set (0.00 sec)2. 多表查询:关联多张表取数
实际开发中,数据常分散在多张表(如员工表emp、部门表dept、工资等级表salgrade),需通过「关联字段」(如deptno)将表连接,获取完整信息。
2.1 两表关联查询
核心逻辑:找到两张表的共同字段(关联字段),用where指定关联条件,避免笛卡尔积(数据重复)。
- 需求 1:显示雇员名、工资及所在部门名称(关联emp和dept)
            
            
              cpp
              
              
            
          
          select e.ename, e.sal, d.dname 
from emp e, dept d  -- 给表起别名,简化代码
where e.deptno = d.deptno;  -- 关联条件:员工表部门号=部门表部门号- 需求 2:显示 10 号部门的部门名、员工名和工资(关联 + 筛选)
            
            
              cpp
              
              
            
          
          select d.dname, e.ename, e.sal 
from emp e, dept d 
where e.deptno = d.deptno 
  and e.deptno = 10;  -- 额外筛选10号部门2.2 三表关联查询
当需要从三张表取数时,需依次指定表间关联条件,确保数据逻辑正确。
- 需求:显示每个员工的姓名、工资、部门名称及工资等级(关联emp/dept/salgrade)
            
            
              cpp
              
              
            
          
          select e.ename, e.sal, d.dname, s.grade 
from emp e, dept d, salgrade s 
where e.deptno = d.deptno  -- 关联emp和dept
  and e.sal between s.losal and s.hisal;  -- 关联emp和salgrade(工资在等级范围内)3. 自连接:同一张表的 "自我关联"
自连接是特殊的多表查询,指同一张表通过别名视为两张表,解决 "表内数据关联" 场景(如查询员工的上级领导)。
- 需求:显示员工「FORD」的上级领导编号和姓名(emp表中mgr字段是领导的empno)
            
            
              cpp
              
              
            
          
          select leader.empno as 领导编号, leader.ename as 领导姓名 
from emp emp, emp leader  -- 同一张表起两个别名:员工表(emp)、领导表(leader)
where emp.ename = 'FORD'  -- 筛选员工FORD
  and emp.mgr = leader.empno;  -- 关联条件:员工的领导编号=领导的员工编号查询结果(示例):
            
            
              cpp
              
              
            
          
          +--------+-----------+
| 领导编号 | 领导姓名  |
+--------+-----------+
|  007566 | JONES     |
+--------+-----------+
1 row in set (0.00 sec)4. 子查询:嵌套查询的灵活应用
子查询(嵌套查询)指将一个select语句嵌入到另一个 SQL 语句中,按返回结果行数可分为「单行子查询」「多行子查询」,按位置可嵌入where或from子句。
4.1 单行子查询(返回 1 行结果)
适用于 "基于单个值筛选" 的场景,常用=匹配子查询结果。
- 需求:查询与「SMITH」同一部门的所有员工(不含 SMITH)
            
            
              cpp
              
              
            
          
          select * from emp 
where deptno = (select deptno from emp where ename = 'SMITH')  -- 子查询:获取SMITH的部门号
  and ename != 'SMITH';  -- 排除SMITH本人4.2 多行子查询(返回多行结果)
适用于 "基于多个值筛选" 的场景,需搭配in/all/any等关键字。
| 关键字 | 作用说明 | 
|---|---|
| in | 匹配子查询结果中的任意一个值 | 
| all | 匹配所有子查询结果(如 > all表示大于所有值) | 
| any | 匹配任意一个子查询结果(如 > any表示大于其中一个值) | 
- 场景 1:用in查询与 10 号部门岗位相同的员工(不含 10 号部门)
            
            
              cpp
              
              
            
          
          select ename, job, sal, deptno 
from emp 
where job in (select distinct job from emp where deptno = 10)  -- 子查询:10号部门的所有岗位
  and deptno != 10;  -- 排除10号部门- 场景 2:用all查询工资高于 30 号部门所有员工的员工
            
            
              cpp
              
              
            
          
          select ename, sal, deptno 
from emp 
where sal > all(select sal from emp where deptno = 30);  -- 大于30号部门所有工资4.3 子查询嵌入 from 子句
将子查询结果视为「临时表」,用于复杂统计(如查询 "高于部门平均工资的员工")。
- 需求:显示每个高于自己部门平均工资的员工姓名、部门、工资及部门平均工资
            
            
              cpp
              
              
            
          
          select e.ename, e.deptno, e.sal, format(tmp.部门平均工资, 2)  -- format格式化小数
from emp e, 
     (select deptno, avg(sal) as 部门平均工资 from emp group by deptno) tmp  -- 子查询作为临时表tmp
where e.deptno = tmp.deptno  -- 关联员工表和临时表
  and e.sal > tmp.部门平均工资;  -- 筛选高于平均工资的员工5. 合并查询:union 与 union all
当需要合并多个select的结果集时,可使用union或union all,两者核心区别是是否去重。
| 操作符 | 去重情况 | 性能 | 适用场景 | 
|---|---|---|---|
| union | 自动去重 | 较低(需比对去重) | 需避免结果重复 | 
| union all | 不去重 | 较高(直接合并) | 结果无重复或允许重复 | 
- 需求:查询工资高于 4000 或 岗位为「PRESIDENT」的员工
            
            
              cpp
              
              
            
          
          -- union去重(若有重复数据会自动剔除)
select * from emp where sal > 4000 
union 
select * from emp where job = 'PRESIDENT';
-- union all不去重(性能更优,适合确认无重复的场景)
select * from emp where sal > 4000 
union all 
select * from emp where job = 'PRESIDENT';5. 表的连接:内连接与外连接详解
在多表查询中,表的连接方式直接决定了数据的查询范围和结果形态。常用的连接方式分为内连接 和外连接,外连接又可细分为左外连接与右外连接。本节将结合实例,拆解不同连接方式的语法、逻辑及适用场景。
6.1 内连接:只保留匹配的记录
内连接是最常用的连接方式,核心逻辑是只保留两张表中 "关联条件匹配" 的记录 ,不匹配的记录会被过滤掉。本质上,它等同于用where子句筛选两张表的笛卡尔积,我们之前学习的多表查询都属于内连接。
6.1.1 内连接语法
内连接支持两种语法格式,核心都是通过on指定关联条件(推荐用on,逻辑更清晰):
-- 格式1:显式内连接(推荐,明确标注 inner join)
select 字段名 
from 表1 inner join 表2 
on 表1.关联字段 = 表2.关联字段  -- 核心:表间关联条件
and 其他筛选条件;  -- 可选:对结果进一步筛选
-- 格式2:隐式内连接(即之前的多表查询写法)
select 字段名 
from 表1, 表2 
where 表1.关联字段 = 表2.关联字段  -- 用where代替on指定关联条件
and 其他筛选条件;6.1.2 内连接案例
需求:显示员工「SMITH」的姓名和所在部门名称(关联emp和dept表)。
- 
方式 1:隐式内连接(笛卡尔积 + where 筛选) select e.ename, d.dname 
 from emp e, dept d
 where e.deptno = d.deptno -- 关联条件:员工部门号=部门表部门号
 and e.ename = 'SMITH'; -- 筛选条件:员工姓名为SMITH
- 
方式 2:显式内连接( inner join + on)
            
            
              cpp
              
              
            
          
          -- 写法1:筛选条件放在on后
select e.ename, d.dname 
from emp e inner join dept d 
on e.deptno = d.deptno  -- 关联条件
and e.ename = 'SMITH';  -- 筛选条件
-- 写法2:筛选条件放在where后(更易理解,先关联表再筛选)
select e.ename, d.dname 
from emp e inner join dept d 
on e.deptno = d.deptno  -- 先通过on完成表关联
where e.ename = 'SMITH';  -- 再通过where筛选目标员工三种写法的查询结果一致:
+-------+----------+
| ename | dname    |
+-------+----------+
| SMITH | RESEARCH |
+-------+----------+
1 row in set (0.00 sec)6.2 外连接:保留某一张表的全部记录
外连接与内连接的核心区别是:会保留其中一张表的 "全部记录" ,即使这些记录在另一张表中没有匹配项(无匹配的字段会显示NULL)。根据 "保留哪张表",外连接分为左外连接和右外连接。
6.2.1 左外连接:保留左表全部记录
左外连接的逻辑是:以 "左表" 为基准,保留左表的所有记录 ,右表只保留与左表匹配的记录;若右表无匹配项,对应字段显示NULL。
select 字段名 
from 左表 left join 右表 
on 左表.关联字段 = 右表.关联字段;  -- 关联条件(与内连接一致)注:
left join可省略outer(即left outer join),效果相同。
左外连接案例
为了更直观展示 "保留左表全部记录",先创建两张测试表:stu(学生表)和exam(成绩表),其中部分学生无成绩,部分成绩无对应学生。
- 
准备测试数据 -- 1. 创建并插入学生表数据(左表,需保留全部学生) 
 create table stu (id int, name varchar(30));
 insert into stu values(1,'jack'),(2,'tom'),(3,'kity'),(4,'nono');-- 2. 创建并插入成绩表数据(右表,部分成绩无对应学生) 
 create table exam (id int, grade int);
 insert into exam values(1,56),(2,76),(11,8); -- id=11的成绩无对应学生
- 
需求:查询所有学生的成绩,即使学生没有成绩也要显示其个人信息 select s.id as 学生ID, s.name as 学生姓名, e.grade as 成绩 
 from stu s left join exam e
 on s.id = e.id; -- 关联条件:学生ID=成绩表ID
查询结果(关键:学生 kity、nono 无成绩,成绩字段显示 NULL,但仍保留记录):
+--------+----------+--------+
| 学生ID | 学生姓名 | 成绩   |
+--------+----------+--------+
|      1 | jack     |     56 |
|      2 | tom      |     76 |
|      3 | kity     |   NULL |  -- 无成绩,显示NULL
|      4 | nono     |   NULL |  -- 无成绩,显示NULL
+--------+----------+--------+
4 rows in set (0.00 sec)6.2.2 右外连接:保留右表全部记录
右外连接的逻辑与左外连接相反:以 "右表" 为基准,保留右表的所有记录 ,左表只保留与右表匹配的记录;若左表无匹配项,对应字段显示NULL。
select 字段名 
from 左表 right join 右表 
on 左表.关联字段 = 右表.关联字段;  -- 关联条件注:
right join可省略outer(即right outer join),效果相同。
右外连接案例
需求:查询所有成绩记录,即使成绩没有对应学生也要显示成绩信息(以exam表为右表,保留全部成绩)。
- 
方式 1:直接使用右外连接 select s.id as 学生ID, s.name as 学生姓名, e.grade as 成绩 
 from stu s right join exam e
 on s.id = e.id; -- 关联条件:学生ID=成绩表ID
- 
方式 2:等价于 "左表与右表互换的左外连接"右外连接可通过调换表的顺序,用左外连接实现(更符合直觉,推荐): select s.id as 学生ID, s.name as 学生姓名, e.grade as 成绩 
 from exam e left join stu s -- 成绩表作为左表,保留全部成绩
 on e.id = s.id; -- 关联条件不变
两种写法的查询结果一致(关键:id=11 的成绩无对应学生,学生信息显示 NULL,但成绩记录保留):
+--------+----------+--------+
| 学生ID | 学生姓名 | 成绩   |
+--------+----------+--------+
|      1 | jack     |     56 |
|      2 | tom      |     76 |
|   NULL | NULL     |     8  |  -- 无对应学生,显示NULL
+--------+----------+--------+
3 rows in set (0.00 sec)6.2.3 内连接与外连接的核心区别
为了更清晰区分,用表格对比三种连接方式的逻辑差异(以stu左表、exam右表为例):
| 连接方式 | 保留的记录范围 | 无匹配项的处理 | 适用场景 | 
|---|---|---|---|
| 内连接 | 只保留两表匹配的记录 | 不保留无匹配的记录 | 需获取 "双方都有数据" 的结果(如:有成绩的学生) | 
| 左外连接 | 保留左表全部记录,右表匹配记录 | 右表无匹配项显示 NULL | 需 "以左表为基准"(如:所有学生的成绩,含无成绩的) | 
| 右外连接 | 保留右表全部记录,左表匹配记录 | 左表无匹配项显示 NULL | 需 "以右表为基准"(如:所有成绩 | 
要不要我帮你补充一份 MySQL 复合查询核心语法对照表?包含本文所有场景的语法模板、关键字说明和注意事项,方便你日常开发时直接查阅。