说透 SQL 连接:一文讲清 INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN

说透 SQL 连接:一文讲清 INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN

连表查询是 SQL 中最核心、最常用的操作,但很多人对 LEFT JOIN 和 RIGHT JOIN 的理解只停留在"左表全保留,右表匹配"的表面。今天,我们用图解 + 实例 + 深度剖析,彻底把这几种连接搞明白。

在数据库查询中,连接(JOIN)堪称灵魂操作。无论是单表查询还是多表联查,掌握连接的原理都是进阶高手的必经之路。本文将从集合论的角度出发,结合生动的例子,帮你一次性理清 INNER JOIN、LEFT JOIN、RIGHT JOIN 和 FULL OUTER JOIN 的本质区别。


一、为什么需要连接?

现实世界中的数据往往是分散存储的:用户表、订单表、商品表......要获取"购买了某商品的用户信息",就必须把用户表和订单表关联起来。连接就是用来根据两个表之间的共有列(通常是外键关系)合并数据的。


二、四种连接的核心概念速览

假设我们有两个简单的表:

学生表 (student)

id name class_id
1 张三 1
2 李四 2
3 王五 NULL

班级表 (class)

id class_name
1 计算机1班
2 计算机2班
4 计算机3班

注意:学生表中的王五没有班级;班级表中 id=4 的班级没有学生。

我们用这两张表来演示各种连接的结果。

1. INNER JOIN(内连接)

定义 :返回两个表中连接字段相等 的记录行。即只有满足 ON 条件的行才会出现在结果集中。

SQL

sql 复制代码
SELECT * FROM student 
INNER JOIN class ON student.class_id = class.id;

结果

student.id name class_id class.id class_name
1 张三 1 1 计算机1班
2 李四 2 2 计算机2班

王五(class_id 为 NULL)和计算机3班(id=4)都没有匹配上,因此不出现。

适用场景:只关心两个表都有对应关系的数据,比如查询"已分班的学生及其班级信息"。

2. LEFT JOIN(左连接,也称为 LEFT OUTER JOIN)

定义 :返回左表的所有行,即使右表中没有匹配的行。右表中没有匹配的列用 NULL 填充。

SQL

sql 复制代码
SELECT * FROM student 
LEFT JOIN class ON student.class_id = class.id;

结果

student.id name class_id class.id class_name
1 张三 1 1 计算机1班
2 李四 2 2 计算机2班
3 王五 NULL NULL NULL

王五虽然班级为空,但因为是左表(student)的记录,仍然被保留。

适用场景:查询"所有学生及其班级,没有班级的也要显示学生信息"。

3. RIGHT JOIN(右连接,也称为 RIGHT OUTER JOIN)

定义 :返回右表的所有行,即使左表中没有匹配的行。左表中没有匹配的列用 NULL 填充。

SQL

sql 复制代码
SELECT * FROM student 
RIGHT JOIN class ON student.class_id = class.id;

结果

student.id name class_id class.id class_name
1 张三 1 1 计算机1班
2 李四 2 2 计算机2班
NULL NULL NULL 4 计算机3班

计算机3班虽然没有学生,但仍被保留。

适用场景:查询"所有班级及其学生,没有学生的班级也要显示"。

注意RIGHT JOIN 可以完全用 LEFT JOIN 替换(只需交换两表顺序)。实际开发中 LEFT JOIN 使用频率远高于 RIGHT JOIN,但理解它有助于对称思维。

4. FULL OUTER JOIN(全外连接)

定义 :返回左表和右表的所有行 。当某一行在另一表中没有匹配时,对应列填充 NULL。相当于 LEFT JOINRIGHT JOIN 的并集。

SQL (MySQL 不直接支持 FULL OUTER JOIN,但可以用 LEFT JOIN UNION RIGHT JOIN 模拟):

sql 复制代码
SELECT * FROM student 
LEFT JOIN class ON student.class_id = class.id
UNION
SELECT * FROM student 
RIGHT JOIN class ON student.class_id = class.id;

结果

student.id name class_id class.id class_name
1 张三 1 1 计算机1班
2 李四 2 2 计算机2班
3 王五 NULL NULL NULL
NULL NULL NULL 4 计算机3班

适用场景:需要完整的关联信息,比如"两表的全量数据对比"。


三、图解四种连接(ASCII 字符画版)

1. INNER JOIN(内连接)------ 交集

text 复制代码
┌──────────────┐     ┌──────────────┐
│    学生表    │     │    班级表    │
│  ┌───────┐   │     │   ┌───────┐  │
│  │ 匹配行 │◄──┼─────┼──►│ 匹配行 │  │
│  └───────┘   │     │   └───────┘  │
│  其他行 ✗    │     │   其他行 ✗   │
└──────────────┘     └──────────────┘

        结果:只有两个圈重叠的部分

2. LEFT JOIN(左连接)------ 左表全部 + 交集

text 复制代码
┌──────────────┐     ┌──────────────┐
│    学生表    │     │    班级表    │
│  ┌───────┐   │     │   ┌───────┐  │
│  │ 匹配行 │◄──┼─────┼──►│ 匹配行 │  │
│  ├───────┤   │     │   └───────┘  │
│  │ 无匹配行│   │     │   其他行 ✗   │
│  │ (保留) │   │     │             │
│  └───────┘   │     └──────────────┘
└──────────────┘

        结果:左表所有行 + 右表匹配到的列(无匹配补 NULL)

3. RIGHT JOIN(右连接)------ 右表全部 + 交集

text 复制代码
┌──────────────┐     ┌──────────────┐
│    学生表    │     │    班级表    │
│  ┌───────┐   │     │   ┌───────┐  │
│  │ 匹配行 │◄──┼─────┼──►│ 匹配行 │  │
│  └───────┘   │     │   ├───────┤  │
│   其他行 ✗   │     │   │ 无匹配行│  │
│             │     │   │ (保留) │  │
└──────────────┘     │   └───────┘  │
                     └──────────────┘

        结果:右表所有行 + 左表匹配到的列(无匹配补 NULL)

4. FULL OUTER JOIN(全外连接)------ 并集

text 复制代码
┌──────────────┐     ┌──────────────┐
│    学生表    │     │    班级表    │
│  ┌───────┐   │     │   ┌───────┐  │
│  │ 匹配行 │◄──┼─────┼──►│ 匹配行 │  │
│  ├───────┤   │     │   ├───────┤  │
│  │ 无匹配行│   │     │   │ 无匹配行│  │
│  │ (保留) │   │     │   │ (保留) │  │
│  └───────┘   │     │   └───────┘  │
└──────────────┘     └──────────────┘

        结果:左表所有行 + 右表所有行(合并去重,无匹配补 NULL)

四、过滤条件放在 WHERE 还是 ON?------ 经典陷阱

很多初学者在使用 LEFT JOIN 时,误将过滤条件放在 WHERE 子句中,导致左连接"退化"为内连接。

错误示例

sql 复制代码
SELECT * FROM student 
LEFT JOIN class ON student.class_id = class.id
WHERE class.class_name = '计算机1班';

意图是想保留所有学生,同时只显示班级为"计算机1班"的匹配信息。但 WHERE 会在连接完成后过滤,由于不匹配的行中 class_name 为 NULL,被 WHERE 条件过滤掉,结果只剩下匹配的那一行(相当于 INNER JOIN)。

正确做法:把过滤条件放在 ON 子句中:

sql 复制代码
SELECT * FROM student 
LEFT JOIN class ON student.class_id = class.id AND class.class_name = '计算机1班';

此时左表所有行保留,不匹配的班级信息显示 NULL。

经验法则

  • ON 决定如何连接两个表(哪些行匹配)
  • WHERE 决定连接后哪些行被保留

对于 INNER JOIN,放在 ON 和 WHERE 效果相同(因为不匹配的行本来就不会出现);但对于 OUTER JOIN(LEFT/RIGHT/FULL),两者结果可能完全不同。


五、性能与优化建议

  1. 优先使用 INNER JOIN:如果业务逻辑不需要保留未匹配的行,INNER JOIN 通常比 OUTER JOIN 更快,因为数据库优化器有更多选择(如使用索引合并)。
  2. 小表驱动大表:在连接条件上,让返回行数较少的表作为驱动表(LEFT JOIN 的左表就是驱动表)。
  3. 确保连接列有索引:无论是 INNER 还是 OUTER JOIN,连接列上的索引至关重要,尤其是当表数据量较大时。
  4. **避免 SELECT ***:只选择需要的列,减少数据传输和内存开销。
  5. 小心 LEFT JOIN 产生笛卡尔积:如果左表的一行在右表有多行匹配,结果集会膨胀。这是连接的正常行为,但要注意是否符合业务预期。

六、其他连接类型

1. CROSS JOIN(交叉连接)

返回两表的笛卡尔积,即左表每一行与右表每一行组合。很少显式使用,但可能因忘记写 ON 条件而意外产生。

2. NATURAL JOIN(自然连接)

自动根据两个表中同名的列进行等值连接。不推荐使用,因为不可控(容易因表结构变化导致结果异常)。

3. SELF JOIN(自连接)

一个表与自身连接,通常用于查询层级关系(如员工-经理)。可以配合 INNER/LEFT 使用。


七、实战案例:复杂查询中的 LEFT JOIN

假设有三张表:orders(订单)、customers(客户)、payments(支付)。要查询所有客户及其订单和支付信息,即使没有订单或支付的客户也要显示。

sql 复制代码
SELECT 
    c.customer_id,
    c.name,
    o.order_id,
    p.amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
LEFT JOIN payments p ON o.order_id = p.order_id;

这条查询:

  • 保留了所有客户(第一个 LEFT JOIN)
  • 即使某个客户没有订单,orders 列为 NULL;即使某个订单没有支付,payments 列为 NULL。
  • 结果是客户的全量视图。

如果换成 INNER JOIN,那些没有订单的客户就会被过滤掉。


八、面试高频问题

Q1:LEFT JOIN 和 RIGHT JOIN 可以互换吗?

A:可以,只需交换表的顺序。A LEFT JOIN B 等价于 B RIGHT JOIN A。通常建议统一使用 LEFT JOIN 以保持代码风格一致。

Q2:FULL OUTER JOIN 在 MySQL 中如何实现?

A:LEFT JOIN UNION RIGHT JOIN,注意 UNION 会去重,如果不需要去重可以用 UNION ALL(但通常 LEFT 和 RIGHT 的结果没有重复行,因为两边的 NULL 行不同)。

Q3:ON 条件中可以使用 != 吗?

A:可以,但非常少见。例如 LEFT JOIN ... ON A.id != B.id 会产生近乎笛卡尔积的结果,通常不是业务想要的效果。

Q4:多个 LEFT JOIN 的执行顺序?

A:按书写顺序从左到右逻辑处理,但数据库优化器可能会重排。建议用括号明确优先级。

Q5:如何判断一个查询应该用 INNER JOIN 还是 LEFT JOIN?

A:问自己:"是否必须保留左表的全部行,即使右表没有匹配?" 如果答案是"是",用 LEFT JOIN;否则用 INNER JOIN。


九、总结

连接类型 结果集特征 典型关键词
INNER JOIN 只返回匹配的行 "两个表都有"
LEFT JOIN 左表全部 + 右表匹配 "以左表为基准"
RIGHT JOIN 右表全部 + 左表匹配 "以右表为基准"
FULL OUTER JOIN 两个表全部 "全部数据"

记住一句话:LEFT JOIN 以左表为尊,右表随缘;INNER JOIN 双方门当户对;FULL JOIN 海纳百川。

掌握了这些,你就能应对 90% 以上的连表查询需求。

思考题

  • 对于 OUTER JOIN,若 ON 条件中包含对右表的非空字段过滤(如 RIGHT_TABLE.column = 'value'),为什么结果可能会变成 INNER JOIN?如何避免?
  • 在多表 LEFT JOIN 中,如果某个中间表没有匹配,后续的 LEFT JOIN 还能关联到数据吗?

欢迎评论区讨论!

如果觉得有帮助,点赞、收藏、转发~


本文首发于 CSDN,未经授权禁止转载。

相关推荐
杨云龙UP2 小时前
Oracle / ODA环境TRACE、alert日志定位与ADRCI清理 SOP_20260423
linux·运维·服务器·数据库·oracle
fengxin_rou2 小时前
黑马点评实战篇|第七篇:Redis消息队列
数据库·redis·缓存
科技小花2 小时前
测评|2026五大数据治理平台横向对比:谁在定义数据中台的“智能引擎”?
大数据·数据库·人工智能·数据治理·数据中台
千月落2 小时前
Redis Cluster 集群部署
数据库·redis·缓存
Chockmans2 小时前
春秋云境CVE-2008-4732
sql·安全·web安全·系统安全·安全威胁分析·春秋云境·cve-2008-4732
撩得Android一次心动2 小时前
Android Room 数据库详解【使用篇】
android·数据库·room·jetpack
yaoxin5211232 小时前
388. Java IO API - 处理事件
java·服务器·数据库
colofullove2 小时前
推导中异常处理
数据库·oracle
黑牛儿2 小时前
MySQL主流存储引擎深度解析:优缺点对比+实操选型指南
数据库·mysql