【MySQL篇】内外连接:多表关联的完整指南

文章目录

    • 内外连接:多表关联的完整指南
    • 一、前言
    • [二、内连接(INNER JOIN)](#二、内连接(INNER JOIN))
      • [2.1 内连接的概念](#2.1 内连接的概念)
      • [2.2 内连接的语法](#2.2 内连接的语法)
        • [方式一:隐式内连接(WHERE 子句)](#方式一:隐式内连接(WHERE 子句))
        • [方式二:显式内连接(INNER JOIN)](#方式二:显式内连接(INNER JOIN))
      • [2.3 内连接示例](#2.3 内连接示例)
      • [2.4 内连接的特点](#2.4 内连接的特点)
    • [三、左外连接(LEFT JOIN)](#三、左外连接(LEFT JOIN))
    • [四、右外连接(RIGHT JOIN)](#四、右外连接(RIGHT JOIN))
    • 五、内外连接的对比
      • [5.1 三种连接的对比表](#5.1 三种连接的对比表)
      • [5.2 可视化对比](#5.2 可视化对比)
      • [5.3 使用场景对比](#5.3 使用场景对比)
    • 六、实战案例
      • [6.1 案例一:查询所有部门及其员工数](#6.1 案例一:查询所有部门及其员工数)
      • [6.2 案例二:查询没有员工的部门](#6.2 案例二:查询没有员工的部门)
      • [6.3 案例三:查询没有成绩的学生](#6.3 案例三:查询没有成绩的学生)
      • [6.4 案例四:查询有成绩但学生不存在的记录](#6.4 案例四:查询有成绩但学生不存在的记录)
    • 七、多表外连接
      • [7.1 三表左外连接](#7.1 三表左外连接)
      • [7.2 混合连接](#7.2 混合连接)
    • [八、LEFT JOIN vs RIGHT JOIN 的互换](#八、LEFT JOIN vs RIGHT JOIN 的互换)
      • [8.1 等价转换](#8.1 等价转换)
      • [8.2 最佳实践](#8.2 最佳实践)
    • 九、常见错误
      • [9.1 错误一:在 WHERE 中过滤外连接](#9.1 错误一:在 WHERE 中过滤外连接)
      • [9.2 错误二:混淆 NULL 的含义](#9.2 错误二:混淆 NULL 的含义)
      • [9.3 错误三:GROUP BY 中缺少列](#9.3 错误三:GROUP BY 中缺少列)
    • 十、总结与最佳实践
      • [10.1 快速判断方法](#10.1 快速判断方法)
      • [10.2 性能考虑](#10.2 性能考虑)
      • [10.3 建议练习](#10.3 建议练习)

内外连接:多表关联的完整指南

一、前言

💬 这一篇讲什么:表的内连接和外连接

🚀 核心内容

  • 什么是内连接?
  • 什么是左外连接?
  • 什么是右外连接?
  • 如何选择合适的连接方式?

在前一篇中,我们学习了多表查询的基础。这一篇将深入讲解内连接和外连接的区别,以及它们在实际应用中的使用场景。这是 MySQL 学习中的重点,也是面试的常考题。


二、内连接(INNER JOIN)

2.1 内连接的概念

内连接只返回两个表中都有匹配的行。换句话说,如果某行在一个表中没有对应的匹配行,就不会出现在结果中。

2.2 内连接的语法

方式一:隐式内连接(WHERE 子句)
sql 复制代码
SELECT column_list
FROM table1, table2
WHERE table1.key = table2.key AND other_conditions;

这是我们前面学习的方式,通过 WHERE 子句指定连接条件。

方式二:显式内连接(INNER JOIN)
sql 复制代码
SELECT column_list
FROM table1
INNER JOIN table2 ON table1.key = table2.key
[WHERE other_conditions];

这是现代 SQL 的标准写法,更清晰、更规范。

2.3 内连接示例

准备测试数据
sql 复制代码
-- 创建员工表
CREATE TABLE emp (
    empno INT PRIMARY KEY,
    ename VARCHAR(20),
    deptno INT
);

-- 创建部门表
CREATE TABLE dept (
    deptno INT PRIMARY KEY,
    dname VARCHAR(20)
);

-- 插入员工数据
INSERT INTO emp VALUES 
(7369, 'SMITH', 20),
(7499, 'ALLEN', 30),
(7521, 'WARD', 30),
(7566, 'JONES', 20),
(7698, 'BLAKE', 30),
(7782, 'CLARK', 10),
(7788, 'SCOTT', 20),
(7839, 'KING', 10),
(7844, 'TURNER', 30),
(7876, 'ADAMS', 20),
(7900, 'JAMES', 30),
(7902, 'FORD', 20),
(7934, 'MILLER', 10);

-- 插入部门数据
INSERT INTO dept VALUES 
(10, '财务部'),
(20, '研发部'),
(30, '销售部'),
(40, '行政部');
示例一:查询 SMITH 的名字和部门名称

方式一:隐式内连接

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

输出:

bashooo 复制代码
+-------+------+
| ename | dname|
+-------+------+
| SMITH | 研发部|
+-------+------+
示例二:查询所有员工的名字和部门名称
sql 复制代码
SELECT e.ename, d.dname
FROM emp e
INNER JOIN dept d ON e.deptno = d.deptno;

输出:

bash 复制代码
+--------+------+
| ename  | dname|
+--------+------+
| SMITH  | 研发部|
| ALLEN  | 销售部|
| WARD   | 销售部|
| JONES  | 研发部|
| BLAKE  | 销售部|
| CLARK  | 财务部|
| SCOTT  | 研发部|
| KING   | 财务部|
| TURNER | 销售部|
| ADAMS  | 研发部|
| JAMES  | 销售部|
| FORD   | 研发部|
| MILLER | 财务部|
+--------+------+

说明:所有 13 个员工都有对应的部门,所以都显示了。行政部(40)没有员工,所以不在结果中。

2.4 内连接的特点

特点 说明
返回行数 只返回两表都有匹配的行
NULL 处理 连接条件为 NULL 的行被排除
使用频率 最常用的连接方式
性能 通常性能最好

三、左外连接(LEFT JOIN)

3.1 左外连接的概念

左外连接返回左表的所有行,以及右表中匹配的行。如果右表中没有匹配的行,右表的列显示为 NULL。

3.2 左外连接的语法

sql 复制代码
SELECT column_list
FROM table1
LEFT JOIN table2 ON table1.key = table2.key
[WHERE other_conditions];

或者使用 LEFT OUTER JOIN(效果相同):

sql 复制代码
SELECT column_list
FROM table1
LEFT OUTER JOIN table2 ON table1.key = table2.key;

3.3 左外连接示例

准备测试数据
sql 复制代码
-- 创建学生表
CREATE TABLE stu (
    id INT PRIMARY KEY,
    name VARCHAR(30)
);

-- 创建成绩表
CREATE TABLE exam (
    id INT,
    grade INT
);

-- 插入学生数据
INSERT INTO stu VALUES 
(1, 'jack'),
(2, 'tom'),
(3, 'kity'),
(4, 'nono');

-- 插入成绩数据
INSERT INTO exam VALUES 
(1, 56),
(2, 76),
(11, 88);  -- 学号 11 的学生不存在
示例一:查询所有学生的成绩

使用内连接

sql 复制代码
SELECT s.id, s.name, e.grade
FROM stu s
INNER JOIN exam e ON s.id = e.id;

输出:

bash 复制代码
+----+------+-------+
| id | name | grade |
+----+------+-------+
| 1  | jack | 56    |
| 2  | tom  | 76    |
+----+------+-------+

问题:学生 kity 和 nono 没有成绩,所以没有显示。

使用左外连接

sql 复制代码
SELECT s.id, s.name, e.grade
FROM stu s
LEFT JOIN exam e ON s.id = e.id;

输出:

bash 复制代码
+----+------+-------+
| id | name | grade |
+----+------+-------+
| 1  | jack | 56    |
| 2  | tom  | 76    |
| 3  | kity | NULL  |
| 4  | nono | NULL  |
+----+------+-------+

说明:所有学生都显示了,即使没有成绩也显示 NULL。

示例二:查询所有部门及其员工
sql 复制代码
SELECT d.deptno, d.dname, e.ename
FROM dept d
LEFT JOIN emp e ON d.deptno = e.deptno
ORDER BY d.deptno;

输出:

bash 复制代码
+--------+------+--------+
| deptno | dname| ename  |
+--------+------+--------+
| 10     | 财务部| CLARK  |
| 10     | 财务部| KING   |
| 10     | 财务部| MILLER |
| 20     | 研发部| SMITH  |
| 20     | 研发部| JONES  |
| 20     | 研发部| SCOTT  |
| 20     | 研发部| ADAMS  |
| 20     | 研发部| FORD   |
| 30     | 销售部| ALLEN  |
| 30     | 销售部| WARD   |
| 30     | 销售部| BLAKE  |
| 30     | 销售部| TURNER |
| 30     | 销售部| JAMES  |
| 40     | 行政部| NULL   |
+--------+------+--------+

说明:行政部(40)没有员工,但仍然显示,员工名为 NULL。

3.4 左外连接的特点

特点 说明
返回行数 返回左表的所有行
右表不匹配 右表列显示为 NULL
使用场景 需要保留左表的所有数据
常见用途 查找没有关联数据的记录

四、右外连接(RIGHT JOIN)

4.1 右外连接的概念

右外连接返回右表的所有行,以及左表中匹配的行。如果左表中没有匹配的行,左表的列显示为 NULL。

4.2 右外连接的语法

sql 复制代码
SELECT column_list
FROM table1
RIGHT JOIN table2 ON table1.key = table2.key
[WHERE other_conditions];

或者使用 RIGHT OUTER JOIN(效果相同):

sql 复制代码
SELECT column_list
FROM table1
RIGHT OUTER JOIN table2 ON table1.key = table2.key;

4.3 右外连接示例

示例一:查询所有成绩及对应的学生
sql 复制代码
SELECT s.id, s.name, e.grade
FROM stu s
RIGHT JOIN exam e ON s.id = e.id;

输出:

bash 复制代码
+------+------+-------+
| id   | name | grade |
+------+------+-------+
| 1    | jack | 56    |
| 2    | tom  | 76    |
| NULL | NULL | 88    |
+------+------+-------+

说明:所有成绩都显示了,包括学号 11 的成绩(学生不存在,所以学生信息为 NULL)。

示例二:查询所有员工及其部门
sql 复制代码
SELECT d.deptno, d.dname, e.ename
FROM emp e
RIGHT JOIN dept d ON e.deptno = d.deptno
ORDER BY d.deptno;

输出与左外连接相同(因为这里 dept 在右边)。

4.4 右外连接的特点

特点 说明
返回行数 返回右表的所有行
左表不匹配 左表列显示为 NULL
使用场景 需要保留右表的所有数据
与左外连接的关系 A RIGHT JOIN B = B LEFT JOIN A

五、内外连接的对比

5.1 三种连接的对比表

连接类型 语法 返回行 说明
内连接 INNER JOIN 两表都匹配 只返回有对应关系的行
左外连接 LEFT JOIN 左表全部 + 右表匹配 保留左表所有行
右外连接 RIGHT JOIN 左表匹配 + 右表全部 保留右表所有行

5.2 可视化对比

假设有两个集合 A(左表)和 B(右表):

bash 复制代码
内连接(INNER JOIN):
    A ∩ B
    只返回交集部分

左外连接(LEFT JOIN):
    A ∪ (A ∩ B)
    返回 A 的全部 + B 中与 A 匹配的部分

右外连接(RIGHT JOIN):
    (A ∩ B) ∪ B
    返回 A 中与 B 匹配的部分 + B 的全部

5.3 使用场景对比

场景 推荐连接 原因
查询有对应关系的数据 INNER JOIN 只需要匹配的数据
查询所有学生及其成绩 LEFT JOIN 需要显示没有成绩的学生
查询所有成绩及对应学生 RIGHT JOIN 需要显示没有学生的成绩
查询所有部门及员工 LEFT JOIN 需要显示没有员工的部门
统计缺失数据 LEFT/RIGHT JOIN 找出没有关联的记录

六、实战案例

6.1 案例一:查询所有部门及其员工数

需求:显示所有部门的名称、部门号、地址和员工数,包括没有员工的部门。

sql 复制代码
SELECT 
    d.deptno,
    d.dname,
    d.loc,
    COUNT(e.empno) AS 员工数
FROM dept d
LEFT JOIN emp e ON d.deptno = e.deptno
GROUP BY d.deptno, d.dname, d.loc
ORDER BY d.deptno;

输出:

bash 复制代码
+--------+------+------+------+
| deptno | dname| loc  | 员工数|
+--------+------+------+------+
| 10     | 财务部| 北京 | 3    |
| 20     | 研发部| 上海 | 5    |
| 30     | 销售部| 深圳 | 5    |
| 40     | 行政部| 广州 | 0    |
+--------+------+------+------+

6.2 案例二:查询没有员工的部门

需求:找出没有任何员工的部门。

sql 复制代码
SELECT d.deptno, d.dname
FROM dept d
LEFT JOIN emp e ON d.deptno = e.deptno
WHERE e.empno IS NULL;

输出:

bash 复制代码
+--------+------+
| deptno | dname|
+--------+------+
| 40     | 行政部|
+--------+------+

6.3 案例三:查询没有成绩的学生

需求:找出所有没有成绩的学生。

sql 复制代码
SELECT s.id, s.name
FROM stu s
LEFT JOIN exam e ON s.id = e.id
WHERE e.grade IS NULL;

输出:

bash 复制代码
+----+------+
| id | name |
+----+------+
| 3  | kity |
| 4  | nono |
+----+------+

6.4 案例四:查询有成绩但学生不存在的记录

需求:找出所有有成绩但对应学生不存在的记录。

sql 复制代码
SELECT e.id, e.grade
FROM stu s
RIGHT JOIN exam e ON s.id = e.id
WHERE s.id IS NULL;

输出:

bash 复制代码
+----+-------+
| id | grade |
+----+-------+
| 11 | 88    |
+----+-------+

七、多表外连接

7.1 三表左外连接

sql 复制代码
SELECT 
    d.deptno,
    d.dname,
    e.ename,
    s.grade
FROM dept d
LEFT JOIN emp e ON d.deptno = e.deptno
LEFT JOIN salgrade s ON e.sal BETWEEN s.losal AND s.hisal
ORDER BY d.deptno;

这个查询返回所有部门,即使没有员工也显示。

7.2 混合连接

sql 复制代码
-- 内连接 + 左外连接
SELECT 
    d.deptno,
    d.dname,
    e.ename,
    e.sal
FROM dept d
INNER JOIN emp e ON d.deptno = e.deptno
LEFT JOIN salgrade s ON e.sal BETWEEN s.losal AND s.hisal
WHERE d.deptno IN (10, 20);

八、LEFT JOIN vs RIGHT JOIN 的互换

8.1 等价转换

以下两个查询是等价的:

方式一:左外连接

sql 复制代码
SELECT d.dname, e.ename
FROM dept d
LEFT JOIN emp e ON d.deptno = e.deptno;

方式二:右外连接

sql 复制代码
SELECT d.dname, e.ename
FROM emp e
RIGHT JOIN dept d ON d.deptno = e.deptno;

说明A LEFT JOIN B 等价于 B RIGHT JOIN A

8.2 最佳实践

  • 优先使用 LEFT JOIN:比 RIGHT JOIN 更直观。
  • 避免混用:在同一个查询中尽量不混用 LEFT 和 RIGHT。
  • 明确主表:LEFT JOIN 的左表是主表,应该是你想要保留所有数据的表。

九、常见错误

9.1 错误一:在 WHERE 中过滤外连接

sql 复制代码
-- 错误:WHERE 条件会把外连接变成内连接
SELECT s.id, s.name, e.grade
FROM stu s
LEFT JOIN exam e ON s.id = e.id
WHERE e.grade IS NOT NULL;  -- 这样就变成内连接了

-- 正确:如果需要过滤,应该在 ON 子句中
SELECT s.id, s.name, e.grade
FROM stu s
LEFT JOIN exam e ON s.id = e.id AND e.grade IS NOT NULL;

9.2 错误二:混淆 NULL 的含义

sql 复制代码
-- 错误:用 = NULL 比较
SELECT * FROM stu s
LEFT JOIN exam e ON s.id = e.id
WHERE e.id = NULL;  -- 永远返回空结果

-- 正确:用 IS NULL
SELECT * FROM stu s
LEFT JOIN exam e ON s.id = e.id
WHERE e.id IS NULL;

9.3 错误三:GROUP BY 中缺少列

sql 复制代码
-- 错误
SELECT d.dname, COUNT(*)
FROM dept d
LEFT JOIN emp e ON d.deptno = e.deptno
GROUP BY d.deptno;

-- 正确
SELECT d.dname, COUNT(*)
FROM dept d
LEFT JOIN emp e ON d.deptno = e.deptno
GROUP BY d.deptno, d.dname;

十、总结与最佳实践

现在你已经掌握了:

内连接:只返回两表都匹配的行

左外连接:返回左表全部 + 右表匹配的行

右外连接:返回左表匹配 + 右表全部的行

连接的选择:根据需求选择合适的连接方式

实战案例:查询缺失数据、统计等常见场景

常见错误:避免在外连接中犯错

10.1 快速判断方法

问题:应该用哪种连接?

判断流程

  1. 需要保留哪个表的所有数据?

    • 左表 → LEFT JOIN
    • 右表 → RIGHT JOIN
    • 都需要 → 用 UNION 或 FULL OUTER JOIN
  2. 是否需要显示没有关联的数据?

    • 是 → 外连接
    • 否 → 内连接
  3. 如何验证?

    • 先用内连接查询,看结果行数。
    • 再用外连接查询,看是否增加了行数。

10.2 性能考虑

  • 内连接性能最好:因为数据量最少。
  • 外连接性能较差:需要处理 NULL 值。
  • 避免不必要的外连接:如果不需要保留所有数据,用内连接。

10.3 建议练习

  1. 用内连接和左外连接分别查询相同的数据,对比结果。
  2. 找出数据库中所有没有关联的记录。
  3. 统计每个分类的数据数量,包括没有数据的分类。
  4. 在你的项目中应用这些技术。

下一篇,我们将学习索引与性能优化:如何创建索引、索引的类型、如何优化查询性能等。

相关推荐
KKKlucifer2 小时前
三权分立 + AI 审计:解析国内堡垒机的合规与智能双引擎
大数据·数据库·人工智能
空太Jun2 小时前
Redis 5大核心数据类型与持久化实战
数据库·redis·缓存
Zender Han2 小时前
VS Code 开发 Flutter 常用快捷键和插件工具详解
android·vscode·flutter·ios
Java面试题总结2 小时前
Spring Boot 包扫描新姿势:AutoScan vs @Import vs @ComponentScan 深度对比
java·数据库·spring boot
geovindu2 小时前
go: Model,Interface,DAL ,Factory,BLL using mysql
开发语言·mysql·设计模式·golang·软件构建
Ruihong2 小时前
你的 Vue 3 生命周期,VuReact 会编译成什么样的 React?
vue.js·react.js·面试
未秃头的程序猿2 小时前
💥 MyBatis 面试连环炮:从源码原理到实战避坑,彻底拿下 Offer 通关秘籍
后端·面试·mybatis
over6972 小时前
面试官视角:TypeScript Pick 工具类型深度解析与手写实现
前端·面试
wfsm2 小时前
安卓环境配置
android