目录
JOIN 是什么?
JOIN 本质上是“笛卡尔积 + 条件筛选”。
在关系数据库中,两个表的连接最原始的操作就是笛卡尔积,即将表 A 和表 B 的所有行做组合,然后通过连接条件筛选我们真正需要的数据。
本篇文章将从以下几个方面,带你走进 JOIN 的进阶世界:
外连接的完整逻辑
更优雅的连接方式:
USING
子句自动化但需谨慎使用的
NATURAL JOIN
🔹OUTER JOIN(外连接)
在实际开发中,我们常常使用 INNER JOIN
来筛选匹配成功的数据行。然而,这种连接方式默认忽
略未匹配的记录,在某些业务场景(如数据缺失分析、审计对比、错误检测)中反而是不利的。
外连接是一种可以保留未匹配记录的连接方式,它不仅保留匹配成功的行,还会保留某一方或双方
表中未能匹配的行,并对缺失部分用 NULL
补全。
外连接的分类
类型 | 含义说明 |
---|---|
LEFT OUTER JOIN | 保留左表所有记录,右表无法匹配的补 NULL |
RIGHT OUTER JOIN | 保留右表所有记录,左表无法匹配的补 NULL |
FULL OUTER JOIN | 保留左右两表所有记录,无法匹配部分用 NULL 补全 |
📌 注意:通常 SQL 中 OUTER
关键字可以省略。
外连接与内连接的区别
比较维度 | INNER JOIN | OUTER JOIN(LEFT/RIGHT/FULL) |
---|---|---|
匹配条件 | 只保留匹配成功的行 | 匹配成功 + 不匹配的行(补 NULL) |
连接结果 | 小于等于原始行数 | 大于等于参与表的行数 |
典型使用场景 | 严格一一匹配需求 | 需要查看未关联数据 |
⚠️ MySQL 不直接支持 FULL OUTER JOIN
🔹USING
子句
我们在之前的 JOIN 中,经常会写出类似 ON a.id = b.id
的重复性语句。USING
是一种语法糖,
当连接字段在两个表中字段名完全一致时,它可以大幅度简化写法。
USING
子句是 JOIN
中的语法简写,用于当连接字段在两个表中字段名相同时,代替 ON a.col = b.col
的写法。
语法结构
SELECT column_list
FROM table1
JOIN table2
USING (column_name);
多个字段:
SELECT column_list
FROM table1
JOIN table2
USING (column1, column2);
🔸 支持:INNER JOIN
、LEFT JOIN
、RIGHT JOIN
、FULL OUTER JOIN
🔸 不支持:CROSS JOIN
(因为它没有连接条件)
⚠️ 注意事项
USING
中指定的字段必须在两个表中都存在且字段名一致仅支持等值连接(
=
)SELECT 出来的结果中,这些字段只会显示一次(不是 table1.col 和 table2.col,而是一个简化字段)
和 ON 的对比
使用 ON
的写法:
SELECT *
FROM employees e
JOIN departments d
ON e.dept_id = d.dept_id;
等价的 USING
写法:
SELECT *
FROM employees
JOIN departments
USING (dept_id);
💡 两者效果一致,差异是:
ON
写法结果中会保留e.dept_id
和d.dept_id
两列;USING
写法只保留一列dept_id
,更整洁。
📘USING 的内部逻辑
在执行阶段,SQL 引擎会将:
JOIN USING (col)
转换为:
JOIN ON table1.col = table2.col
然后只保留一个 col 字段,不显示来源表名。
🧩 多个字段的 USING
你可以使用多个字段作为连接条件(必须字段名都一样):
SELECT *
FROM orders o
JOIN deliveries d
USING (order_id, delivery_date);
这相当于:
ON o.order_id = d.order_id AND o.delivery_date = d.delivery_date
USING 的 SELECT 特性(字段可见性)
考虑如下例子:
SELECT *
FROM employees
JOIN departments
USING (dept_id);
结果中:
dept_id
只出现一次你不能在
SELECT
中写employees.dept_id
或departments.dept_id
,会报错
💡 所以在使用 USING
时,如果你需要区分字段来源,就不要用 USING
,用 ON
更灵活
配合 JOIN 类型的效果
JOIN 类型 | 是否支持 USING | 非匹配字段是否保留 | 输出字段 |
---|---|---|---|
INNER JOIN | ✅ | 否 | 公共字段只显示一次 |
LEFT JOIN | ✅ | 左表全部保留 | 同上 |
RIGHT JOIN | ✅ | 右表全部保留 | 同上 |
FULL OUTER JOIN | ✅(部分数据库) | 两表全部保留 | 同上 |
🔹NATURAL JOIN(自然连接)
NATURAL JOIN
是一种自动基于两个表中相同列名进行连接的语法,无需显式指定连接条件。
基本语法:
SELECT column_list
FROM table1
NATURAL JOIN table2;
支持的连接类型有:
NATURAL JOIN
(即自然内连接)NATURAL LEFT JOIN
NATURAL RIGHT JOIN
NATURAL FULL OUTER JOIN
(部分数据库支持)
🔄 执行机制
自然连接的执行过程分为三步:
自动识别两个表中字段名相同的列
对这些列执行等值连接(相当于 ON a.col = b.col)
结果中只保留一份这些列,不重复显示
注意:这是隐式行为,你无法控制哪些字段被用于连接,只要名字一样,它就会加入连接条件中。
🔍 对比示例
我们以两个表为例:
表A:students
id |
name |
class_id |
---|---|---|
1 |
Alice |
101 |
2 |
Bob |
102 |
表B:classes
class_id | teacher |
---|---|
101 | Zhang |
102 | Wang |
使用 NATURAL JOIN:
SELECT *
FROM students
NATURAL JOIN classes;
相当于:
SELECT students.id, students.name, students.class_id, classes.teacher
FROM students
JOIN classes
ON students.class_id = classes.class_id;
但注意:
class_id
只显示一列,不是students.class_id
和classes.class_id
两列。
隐藏风险 ⚠️
虽然语法简洁,但 NATURAL JOIN
有两个致命隐患:
1️⃣ 字段名意外冲突
如果两个表中存在同名但不相关的列,它们也会被自动用于连接,可能导致:
错误连接逻辑
无结果
数据误导
示例:
-- students 和 exams 表都有 name 字段,但不是用于连接的
SELECT *
FROM students
NATURAL JOIN exams;
这里 name
字段会被用于连接,结果可能完全不符合预期。
2️⃣ 表结构变动敏感
只要某个字段名在两个表中变得相同(或不同),NATURAL JOIN
的行为会自动改变,增加 SQL 的不可预测性与维护难度。
字段选择行为(结果特征)
所有同名字段:只显示一列(不带表前缀)
非同名字段:分别显示原始表字段
自然连接的变体语法(扩展)
类型 | 说明 |
---|---|
NATURAL JOIN |
自然内连接(只返回匹配行) |
NATURAL LEFT JOIN |
左自然外连接(左表全部保留) |
NATURAL RIGHT JOIN |
右自然外连接(右表全部保留) |
NATURAL FULL OUTER JOIN |
全自然外连接(两个表全部保留) |
⚠️ FULL OUTER
在 MySQL 中不支持。
NATURAL JOIN vs USING vs ON
特性 | NATURAL JOIN | USING 子句 | ON 子句 |
---|---|---|---|
连接字段是否自动识别 | ✅(自动找同名字段) | ❌(手动指定) | ❌(完全手动) |
是否必须字段名一致 | ✅ 必须一致 | ✅ 仅 USING 中字段需一致 | ❌ 不要求一致 |
连接条件是否灵活 | ❌ 只能等值 | ❌ 只能等值 | ✅ 任意逻辑、范围比较等 |
SELECT 中字段出现情况 | 同名字段只出现一次 | USING 字段只出现一次 | 字段全部保留 |
可控性 | ❌ 极低 | ✅ 中等 | ✅✅ 极高 |
推荐使用 | ❌ 不推荐 | ✅ 推荐 | ✅✅ 强烈推荐 |
🧩 JOIN 的本质回顾:有条件的笛卡尔积
JOIN 的底层其实是生成两个表的笛卡尔积,再通过连接条件进行筛选,不同 JOIN 类型只是在“保留未匹配数据”的策略上不同。