文章目录
- [第一章 连接查询的本质:笛卡尔积与关联条件](#第一章 连接查询的本质:笛卡尔积与关联条件)
- [第二章 内连接(INNER JOIN):精准匹配的交集](#第二章 内连接(INNER JOIN):精准匹配的交集)
-
- [2.1 标准语法结构](#2.1 标准语法结构)
- [2.2 经典案例实战:查询 SMITH 的部门名称](#2.2 经典案例实战:查询 SMITH 的部门名称)
- [第三章 外连接(OUTER JOIN):主表保全的关联](#第三章 外连接(OUTER JOIN):主表保全的关联)
-
- [3.1 左外连接(LEFT JOIN)](#3.1 左外连接(LEFT JOIN))
- [3.2 右外连接(RIGHT JOIN)](#3.2 右外连接(RIGHT JOIN))
- [第四章 外连接实战演练:学生成绩与部门员工案例](#第四章 外连接实战演练:学生成绩与部门员工案例)
-
- [4.1 案例一:左连接 ------ 展示所有学生及其成绩](#4.1 案例一:左连接 —— 展示所有学生及其成绩)
- [4.2 案例二:右连接 ------ 展示所有成绩及其对应学生](#4.2 案例二:右连接 —— 展示所有成绩及其对应学生)
- [4.3 综合实战:左右连接互换(列出没有员工的部门)](#4.3 综合实战:左右连接互换(列出没有员工的部门))
第一章 连接查询的本质:笛卡尔积与关联条件
在深入探讨内连接和外连接的具体语法之前,我们有必要先理解连接查询的底层数学逻辑。无论是内连接还是外连接,其第一步在数据库内部都是基于两个表做笛卡尔积(Cartesian Product)。
笛卡尔积是指将左表的每一行与右表的每一行进行组合。假设表 A 有 n 行,表 B 有 m 行,那么笛卡尔积的结果将产生 n×m 行临时数据。在实际业务中,这通常是海量且毫无意义的数据。因此,连接查询的核心操作就是在笛卡尔积的基础上,通过关联条件 (通常是主外键关系)筛选出符合业务逻辑的有效数据。ON 子句和 WHERE 子句正是我们用来裁剪这张巨大临时表的工具。理解了这一点,你就能明白为什么无条件的多表查询(即不带 WHERE 的逗号连接)会让数据库陷入巨大的性能灾难。
第二章 内连接(INNER JOIN):精准匹配的交集
内连接是日常开发中最常用的连接查询,它在逻辑上可以理解为:仅返回左表和右表中满足连接条件的交集部分。如果某一行在另一张表中没有匹配项,则该行不会出现在最终结果集中。
2.1 标准语法结构
内连接有两种主流的写法,即隐式语法 (使用逗号和 WHERE)和显式语法 (使用 INNER JOIN 和 ON)。从可读性和现代数据库规范出发,我更推荐使用显式语法,因为它将表与表之间的"连接逻辑"和结果集的"过滤逻辑"分开了。
sql
-- 隐式内连接(传统写法,易读性较差,条件一多容易混乱)
SELECT 字段列表
FROM 表1, 表2
WHERE 连接条件 AND 过滤条件;
-- 显式内连接(标准写法,结构清晰)
SELECT 字段列表
FROM 表1
INNER JOIN 表2 ON 连接条件
WHERE 过滤条件;
各子句拆解:
INNER JOIN:声明连接类型为内连接,INNER关键字通常可以省略,直接写作JOIN。ON子句:专门用于指定两张表之间的关联关系 (例如:EMP.deptno = DEPT.deptno)。这是连接查询的灵魂,决定了笛卡尔积的裁剪逻辑。WHERE子句:用于对连接后生成的临时结果集进行业务维度的二次筛选 (例如:ename='SMITH')。
常见误区 :很多初学者会将关联条件写在 WHERE 中,虽然结果正确,但在复杂查询中会将连接逻辑与业务逻辑混为一谈,极大地降低了 SQL 的可维护性。
2.2 经典案例实战:查询 SMITH 的部门名称
我们以经典的 EMP(员工表)和 DEPT(部门表)为例,演示两种语法的实现。假设 EMP 表中有 ename 和 deptno,DEPT 表中有 deptno 和 dname。
方法一:隐式内连接(旧式写法)
sql
SELECT e.ename, d.dname
FROM EMP e, DEPT d
WHERE e.deptno = d.deptno AND e.ename = 'SMITH';
方法二:显式内连接(标准写法)
sql
SELECT e.ename, d.dname
FROM EMP e
INNER JOIN DEPT d ON e.deptno = d.deptno
WHERE e.ename = 'SMITH';
执行逻辑推演 :
数据库首先计算 EMP 和 DEPT 的笛卡尔积。接着,ON e.deptno = d.deptno 将结果集限制为只有员工所在部门编号与部门表编号匹配的记录。最后,WHERE e.ename = 'SMITH' 在连接后的结果中精准定位到 SMITH 这条记录,并投影出姓名和部门名称。如果 SMITH 的 deptno 在 DEPT 表中不存在,那么内连接将返回空结果。
第三章 外连接(OUTER JOIN):主表保全的关联
与内连接只取交集不同,外连接会强制保留某一边(或两边)的所有数据,即使它们在另一张表中没有匹配项。 MySQL 支持两种外连接:左外连接(LEFT JOIN)和右外连接(RIGHT JOIN)。
3.1 左外连接(LEFT JOIN)
定义:当进行联合查询时,LEFT JOIN 左侧的表(即左表)中的所有记录都会被完整保留到结果集中。如果右表中存在满足 ON 条件的匹配行,则显示右表数据;如果右表中没有匹配行,则右表所有字段以 NULL 填充。
语法结构:
sql
SELECT 字段列表
FROM 表1 -- 主表(完全显示)
LEFT JOIN 表2 -- 从表(匹配不上则显示 NULL)
ON 表1.关联字段 = 表2.关联字段;
适用场景:最常见于"主从数据"查询,例如"查询所有用户及其订单信息,无订单的用户也要显示"。
3.2 右外连接(RIGHT JOIN)
定义:与左外连接恰好相反。RIGHT JOIN 右侧的表(即右表)中的所有记录都会被完整保留到结果集中。如果左表没有匹配,则左表字段以 NULL 填充。
语法结构:
sql
SELECT 字段列表
FROM 表1 -- 从表(匹配不上则显示 NULL)
RIGHT JOIN 表2 -- 主表(完全显示)
ON 表1.关联字段 = 表2.关联字段;
重要心法 :LEFT JOIN 和 RIGHT JOIN 在功能上是完全等价的,本质上互为语法糖。只要将 LEFT JOIN 中左右两表的顺序互换,同时将 LEFT 改为 RIGHT,查询结果将完全一致。因此,在实际开发中,LEFT JOIN 的使用频率远高于 RIGHT JOIN,因为人们更习惯将主表写在前面。
第四章 外连接实战演练:学生成绩与部门员工案例
为了深入理解外连接的特性,我们通过两组具体的案例来验证 NULL 填充的细节和左右连接的互换技巧。
4.1 案例一:左连接 ------ 展示所有学生及其成绩
准备环境 :
首先创建学生表 stu 和成绩表 exam,注意观察两者 ID 的差异(stu 表有 ID 4,exam 表有 ID 11)。
sql
-- 创建学生表
CREATE TABLE stu (
id INT,
name VARCHAR(30)
);
INSERT INTO stu VALUES (1, 'jack'), (2, 'tom'), (3, 'kity'), (4, 'nono');
-- 创建成绩表
CREATE TABLE exam (
id INT,
grade INT
);
INSERT INTO exam VALUES (1, 56), (2, 76), (11, 8);
业务需求:查询所有学生的成绩,如果学生没有成绩,也要显示该学生的个人信息。
分析:需求要求保留所有学生,因此学生表 stu 必须是主表(左表)。
sql
SELECT s.id, s.name, e.grade
FROM stu s
LEFT JOIN exam e ON s.id = e.id;
结果分析:
- 学生
jack(ID=1)匹配到成绩 56,正常显示。 - 学生
tom(ID=2)匹配到成绩 76,正常显示。 - 学生
kity(ID=3)和nono(ID=4)在exam表中无对应记录,因此e.grade字段返回NULL,但学生个人信息完整显示。
4.2 案例二:右连接 ------ 展示所有成绩及其对应学生
业务需求 :对 stu 表和 exam 表联合查询,把所有的成绩都显示出来,即使这个成绩没有学生与它对应。
分析:需求要求保留所有成绩,因此成绩表 exam 必须是主表(右表)。
sql
SELECT s.id, s.name, e.id AS exam_id, e.grade
FROM stu s
RIGHT JOIN exam e ON s.id = e.id;
结果分析:
- 成绩 56(ID=1)和 76(ID=2)分别对应
jack和tom,正常显示。 - 成绩 8(ID=11)在
stu表中查无此人,因此左表stu的s.id和s.name全部返回NULL,但成绩数据 8 被完整保留。
4.3 综合实战:左右连接互换(列出没有员工的部门)
业务需求:列出部门名称和这些部门的员工信息,同时列出没有员工的部门。
这是一个非常经典的"存在缺失关联"的查询场景。需求要求保留所有部门(即使没有员工),因此部门表 DEPT 必须是主表。我们利用左右连接等价性,提供两种完全等效的实现方案。
方案一:使用左外连接(DEPT 作为左表)
sql
SELECT d.dname, e.*
FROM dept d
LEFT JOIN emp e ON d.deptno = e.deptno;
这张 SQL 以部门为主视角,无论部门下是否有员工,部门名称都会出现。如果该部门下无员工,则员工 e.* 的所有字段均为 NULL。
方案二:使用右外连接(DEPT 作为右表)
sql
SELECT d.dname, e.*
FROM emp e
RIGHT JOIN dept d ON d.deptno = e.deptno;
这张 SQL 将 DEPT 放在了 RIGHT JOIN 的右侧,将其变为主表。执行结果与方案一完全一致。
开发建议 :在实际编码中,你完全可以根据业务主语的表述顺序,灵活选用 LEFT 或 RIGHT。但出于代码的一致性考虑,我强烈建议团队统一规范,只使用 LEFT JOIN,将主表固定放在左边,这样可以极大减少因表顺序混乱导致的逻辑误判。