SQL基础-多表连接与集合查询
最近在复习 SQL 的多表查询部分,发现 JOIN 的种类不少,再加上 UNION 集合操作,内容虽然不难但容易混淆,尤其是各种外连接的区别、ON 和 WHERE 在外连接中的不同表现等。所以把这些知识点整理到一起,方便对比学习和日后速查。
本文使用以下示例表来演示所有查询:
1 | -- 部门表 |
💡 表关系说明:
employee.dept_id→department.id(多对一)employee.manager_id→employee.id(自引用,用于自连接)employee↔project通过emp_project关联(多对多)- 刻意设计了一些”不匹配”的数据:员工9、10 没有部门;部门4 没有员工;项目4 没有成员——方便演示各类 JOIN。
一、交叉连接(CROSS JOIN)
交叉连接会将两张表的每一行两两组合,生成笛卡尔积,结果行数 = 表1行数 × 表2行数。
基本语法:
1 | -- 显式写法 |
1 | -- 示例:生成所有"员工 × 项目"的组合 |
⚠️ 注意:交叉连接会产生大量数据(M × N 行),实际开发中很少直接使用。如果在
FROM中用逗号分隔多张表却忘记写 WHERE 条件,就会意外产生笛卡尔积,这是一个常见的性能陷阱。
💡 应用场景:生成所有可能的组合(如商品 × 折扣活动)、填充维度表、配合
WHERE过滤后使用。
二、⭐ 内连接(INNER JOIN)
内连接是最常用的连接方式,只返回两张表中满足连接条件的匹配行,不匹配的行会被丢弃。
基本语法:
1 | SELECT 列名 FROM 表1 |
1 | -- 查询每个员工所属的部门名称 |
结果(8 行,刘一和陈二因 dept_id 为 NULL 被排除,财务部因没有员工也不出现):
| name | salary | dept_name |
|---|---|---|
| 张三 | 15000 | 技术部 |
| 李四 | 18000 | 技术部 |
| 王五 | 12000 | 销售部 |
| 赵六 | 9000 | 销售部 |
| 孙七 | 11000 | 人事部 |
| 周八 | 22000 | 技术部 |
| 吴九 | 15000 | 销售部 |
| 郑十 | 13000 | 人事部 |
⚠️ 注意:内连接只保留两边都能匹配的行。未分配部门的员工(刘一、陈二)和没有员工的部门(财务部)都不会出现在结果中。
隐式内连接(旧写法):
1 | -- 不推荐的旧写法,但需要能认识 |
💡 建议:始终使用
JOIN ... ON的显式写法,将连接条件(ON)和过滤条件(WHERE)分开,可读性更好,也不容易漏写条件导致笛卡尔积。
多表连接:
1 | -- 查询参与了项目的员工:姓名、部门、项目名、角色 |
| name | dept_name | project_name | role |
|---|---|---|---|
| 张三 | 技术部 | 电商平台重构 | 开发 |
| 李四 | 技术部 | 电商平台重构 | 架构师 |
| 李四 | 技术部 | 数据分析系统 | 技术负责人 |
| 王五 | 销售部 | APP 开发 | 销售对接 |
| 周八 | 技术部 | 电商平台重构 | 项目经理 |
| 周八 | 技术部 | 数据分析系统 | 技术顾问 |
| 吴九 | 销售部 | APP 开发 | 销售负责人 |
| 郑十 | 人事部 | 数据分析系统 | 人事协调 |
💡 补充:多表连接时,JOIN 的书写顺序通常不影响最终结果(优化器会自动选择最优执行顺序),但建议按逻辑关系书写,方便阅读和维护。
三、⭐ 外连接(OUTER JOIN)
外连接可以保留某一侧或两侧表中不匹配的行,不匹配的部分用 NULL 填充。
1. 左外连接(LEFT JOIN)
保留左表的所有行,右表无匹配时填充 NULL。
1 | SELECT 列名 FROM 表1 |
1 | -- 查询所有员工及其部门(包括未分配部门的员工) |
结果(10 行,刘一和陈二的部门显示为 NULL):
| name | salary | dept_name |
|---|---|---|
| 张三 | 15000 | 技术部 |
| 李四 | 18000 | 技术部 |
| 王五 | 12000 | 销售部 |
| 赵六 | 9000 | 销售部 |
| 孙七 | 11000 | 人事部 |
| 周八 | 22000 | 技术部 |
| 吴九 | 15000 | 销售部 |
| 郑十 | 13000 | 人事部 |
| 刘一 | 9500 | NULL |
| 陈二 | 8000 | NULL |
⭐ 经典用法:查找”没有匹配”的记录:
1 | -- 查找未分配部门的员工 |
💡 实用技巧:
LEFT JOIN + WHERE 右表.主键 IS NULL是查找”左表有、右表无”记录的经典模式,在数据完整性检查中非常常用。
2. 右外连接(RIGHT JOIN)
保留右表的所有行,左表无匹配时填充 NULL。
1 | SELECT 列名 FROM 表1 |
1 | -- 查询所有部门及其员工(包括没有员工的部门) |
结果(9 行,财务部出现但没有员工):
| dept_name | location | name |
|---|---|---|
| 技术部 | 北京 | 张三 |
| 技术部 | 北京 | 李四 |
| 技术部 | 北京 | 周八 |
| 销售部 | 上海 | 王五 |
| 销售部 | 上海 | 赵六 |
| 销售部 | 上海 | 吴九 |
| 人事部 | 北京 | 孙七 |
| 人事部 | 北京 | 郑十 |
| 财务部 | 深圳 | NULL |
💡 补充:
RIGHT JOIN可以通过交换表的顺序改写为LEFT JOIN,两者本质相同。实际开发中LEFT JOIN更常用,建议统一使用 LEFT JOIN,保持代码风格一致。
1 | -- 上面的 RIGHT JOIN 等价于: |
3. 全外连接(FULL OUTER JOIN)
保留两侧表的所有行,不匹配的部分用 NULL 填充。
⚠️ 注意:MySQL 不支持
FULL OUTER JOIN语法!需要通过LEFT JOIN+UNION ALL+RIGHT JOIN来模拟。
1 | -- MySQL 模拟全外连接 |
💡 为什么用
UNION ALL而不是UNION? 因为第一部分(LEFT JOIN)已经包含了所有左表行和匹配的右表行,第二部分只取右表独有的行(WHERE e.id IS NULL),两部分不会重复,用UNION ALL避免了不必要的去重排序,性能更好。
4. ⭐ 外连接中 ON 和 WHERE 的区别
这是一个非常容易踩坑的地方。在外连接中,条件放在 ON 和 WHERE 中的效果完全不同:
ON中的条件:用于判断连接匹配,不匹配的行仍然保留(填 NULL)WHERE中的条件:在连接之后过滤,会剔除不满足条件的行(包括 NULL 行)
1 | -- ❌ 错误理解:想要"所有员工 + 只显示技术部的部门名" |
⚠️ 记忆口诀:内连接中 ON 和 WHERE 效果一样;外连接中,想保留行就放 ON,想剔除行就放 WHERE。
四、自连接(SELF JOIN)
自连接是指同一张表与自身进行连接,通过不同的别名来区分两个”实例”。常用于处理表中存在层级关系或需要行与行之间比较的场景。
基本语法:
1 | SELECT a.列, b.列 |
1 | -- 查询每个员工及其直属上级的名字 |
结果:
| employee_name | manager_name |
|---|---|
| 张三 | 周八 |
| 李四 | 周八 |
| 王五 | 吴九 |
| 赵六 | 吴九 |
| 孙七 | NULL |
| 周八 | NULL |
| 吴九 | 周八 |
| 郑十 | 孙七 |
| 刘一 | NULL |
| 陈二 | NULL |
⚠️ 注意:自连接必须使用别名(如
e和m),否则 SQL 无法区分是哪个”实例”的列。这里用LEFT JOIN是为了让没有上级的员工(如周八、孙七)也出现在结果中。
更多自连接场景:
1 | -- 查找同一部门中工资更高的员工对 |
💡 自连接的应用场景:
- 查询层级关系(员工 → 上级、类别 → 父类别)
- 同一表中行与行之间的比较(如找同部门工资更高的人)
- 连续日期/序号的记录比较(如找连续登录的用户)
五、JOIN 连接类型对比总结
| 连接类型 | 关键字 | 返回结果 | 典型场景 |
|---|---|---|---|
| 交叉连接 | CROSS JOIN |
所有行的笛卡尔积(M × N) | 生成组合、测试数据 |
| 内连接 | [INNER] JOIN |
仅两边都匹配的行 | 关联查询(最常用) |
| 左外连接 | LEFT [OUTER] JOIN |
左表全部 + 右表匹配 | 保留主表所有记录 |
| 右外连接 | RIGHT [OUTER] JOIN |
右表全部 + 左表匹配 | 同 LEFT JOIN(换方向) |
| 全外连接 | FULL [OUTER] JOIN |
两表全部行 | MySQL 需用 UNION 模拟 |
| 自连接 | 同表 JOIN 自身 + 别名 |
取决于使用的 JOIN 类型 | 层级关系、行间比较 |
⭐ 核心理解:
INNER JOIN→ 取交集(两边都有才返回)LEFT JOIN→ 左表全集 + 右表匹配部分RIGHT JOIN→ 右表全集 + 左表匹配部分FULL JOIN→ 两表并集(MySQL 不直接支持)
六、⭐ UNION 集合操作
UNION 用于将多个 SELECT 语句的结果纵向合并(上下拼接)。与 JOIN 的”横向拼接”不同,UNION 是把多个查询的结果堆叠在一起。
1. UNION(去重合并)
1 | SELECT 列 FROM 表1 WHERE 条件1 |
1 | -- 合并两个查询结果:高薪员工 和 技术部员工(有重叠) |
结果(李四、周八同时满足两个条件,但 UNION 自动去重,只出现一次):
| name | salary |
|---|---|
| 李四 | 18000 |
| 周八 | 22000 |
| 张三 | 15000 |
2. UNION ALL(不去重合并)
1 | SELECT 列 FROM 表1 WHERE 条件1 |
1 | -- 同样的查询,但不去重 |
结果(李四、周八各出现 2 次):
| name | salary |
|---|---|
| 李四 | 18000 |
| 周八 | 22000 |
| 张三 | 15000 |
| 李四 | 18000 |
| 周八 | 22000 |
3. ⭐ UNION vs UNION ALL 对比
| 对比维度 | UNION | UNION ALL |
|---|---|---|
| 是否去重 | ✅ 自动去除重复行 | ❌ 保留所有行(含重复) |
| 性能 | 较慢(需要额外的排序去重操作) | 较快(直接拼接) |
| 使用场景 | 需要去重的合并 | 确定无重复,或不需要去重 |
💡 性能建议:如果确定两个查询的结果不会重复(比如从不同的表查询),或者允许重复,优先使用
UNION ALL,性能更好。
4. UNION 使用注意事项
⚠️ 注意事项:
- 所有
SELECT的列数必须相同,且对应列的数据类型要兼容- 结果集的列名取自第一个
SELECTORDER BY只能放在最后,对整个合并结果排序- 单独的
SELECT中使用ORDER BY需要配合LIMIT才有意义(否则优化器可能忽略)
1 | -- ❌ 错误:两个 SELECT 列数不一致 |
1 | -- ORDER BY 放在最后,对合并结果统一排序 |
1 | -- 各 SELECT 中使用 ORDER BY 需配合 LIMIT(取各部门工资前2名再合并) |
💡 补充:MySQL 8.0.31+ 开始支持
INTERSECT(交集)和EXCEPT(差集)操作:
1
2
3
4
5
6
7
8
9
10
11 -- 同时满足两个条件的员工(交集)
SELECT name FROM employee WHERE salary > 15000
INTERSECT
SELECT name FROM employee WHERE dept_id = 1;
-- 输出: 李四、周八
-- 在技术部但工资不超过 15000 的员工(差集)
SELECT name FROM employee WHERE dept_id = 1
EXCEPT
SELECT name FROM employee WHERE salary > 15000;
-- 输出: 张三
七、JOIN vs UNION 对比
| 对比维度 | JOIN | UNION |
|---|---|---|
| 合并方向 | 横向拼接(增加列) | 纵向拼接(增加行) |
| 表关系 | 通过关联条件连接不同表的列 | 将多个查询结果上下堆叠 |
| 列要求 | 各表列数可以不同 | 各 SELECT 列数必须相同 |
| 典型场景 | 关联查询不同表的字段 | 合并同结构的查询结果 |
八、综合速查表
| 需求 | 语法 | 要点 |
|---|---|---|
| 笛卡尔积 | CROSS JOIN |
M × N 行,慎用 |
| 关联查询(仅匹配行) | [INNER] JOIN ... ON |
最常用的连接方式 |
| 保留左表全部 | LEFT JOIN ... ON |
右表无匹配填 NULL |
| 保留右表全部 | RIGHT JOIN ... ON |
可改写为 LEFT JOIN |
| 保留两边全部 | LEFT JOIN + UNION ALL + RIGHT JOIN |
MySQL 不支持 FULL JOIN |
| 自身关联 | 同表 JOIN + 别名 |
层级关系 / 行间比较 |
| 纵向合并(去重) | UNION |
列数和类型须一致 |
| 纵向合并(不去重) | UNION ALL |
性能更好,优先考虑 |
| 查”左有右无” | LEFT JOIN ... WHERE 右表.pk IS NULL |
数据完整性检查 |
| 外连接保留行的过滤 | 条件放 ON 中 |
放 WHERE 会剔除 NULL 行 |
九、LeetCode 相关练习
🔗 后续待补充..
参考资料
📚 本文内容参考以下资料整理: