MySQL 复合查询全解析:从单表到多表的实战进阶

目录

[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:显示雇员名、工资及所在部门名称(关联empdept
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 语句中,按返回结果行数可分为「单行子查询」「多行子查询」,按位置可嵌入wherefrom子句。

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的结果集时,可使用unionunion 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」的姓名和所在部门名称(关联empdept表)。

  • 方式 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. 准备测试数据

    -- 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的成绩无对应学生

  2. 需求:查询所有学生的成绩,即使学生没有成绩也要显示其个人信息

    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 复合查询核心语法对照表?包含本文所有场景的语法模板、关键字说明和注意事项,方便你日常开发时直接查阅。

相关推荐
Sammyyyyy6 小时前
MySQL 与 PostgreSQL,该怎么选
数据库·mysql·postgresql·开发工具·servbay
程序新视界6 小时前
数据库的分片与分区:有什么区别?
数据库·后端·mysql
tebukaopu14810 小时前
mysql如何强制区分大小写
数据库·mysql
斌阿哥12 小时前
MySql执行计划Explain的原理与应用
mysql
会飞的架狗师14 小时前
【MySQL体系】第8篇:MySQL集群架构技术详解
数据库·mysql·架构
星光一影15 小时前
供应链进销存源码uniapp全开源ERP多仓库管理系统pc+app手机端
mysql·elementui·uni-app·开源·php·phpstorm·1024程序员节
ximy133517 小时前
Mysql基础知识之SQL语句——库表管理操作
sql·mysql·oracle
2501_9387820917 小时前
《Ubuntu 系统下 MySQL 安装前的环境检查与依赖准备指南》
hive·mysql·ubuntu·adb