DevToolBox免费
博客

SQL Join 详解:图解指南与实战示例

12 分钟阅读作者 DevToolBox

SQL 连接是关系型数据库的核心。它允许你根据关联列将两个或多个表的数据组合在一起。尽管连接是基础概念,但对很多开发者来说仍然令人困惑。本指南通过可视化图表和实际示例,让每种连接类型都变得清晰易懂。

示例数据:两张表

本指南中,我们将使用以下两张简单的表:

-- employees table
+----+----------+---------------+
| id | name     | department_id |
+----+----------+---------------+
| 1  | Alice    | 1             |
| 2  | Bob      | 2             |
| 3  | Charlie  | 1             |
| 4  | Diana    | NULL          |
+----+----------+---------------+

-- departments table
+----+-------------+
| id | dept_name   |
+----+-------------+
| 1  | Engineering |
| 2  | Marketing   |
| 3  | Sales       |
+----+-------------+

INNER JOIN(内连接)

只返回两张表中都有匹配值的行。可以理解为两个集合的交集。

    Table A         Table B
  +---------+     +---------+
  |         |     |         |
  |    +----+-----+----+    |
  |    |  INNER JOIN   |    |
  |    +----+-----+----+    |
  |         |     |         |
  +---------+     +---------+
SELECT e.name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;

结果:

+----------+-------------+
| name     | dept_name   |
+----------+-------------+
| Alice    | Engineering |
| Bob      | Marketing   |
| Charlie  | Engineering |
+----------+-------------+
-- Diana excluded (department_id is NULL)
-- Sales excluded (no employee in Sales)

当你只需要两张表中都存在的记录时使用。

LEFT JOIN(左外连接)

返回左表的所有行和右表的匹配行。如果没有匹配,右表列返回 NULL。

    Table A         Table B
  +---------+     +---------+
  |XXXXXXXXX|     |         |
  |XXXX+----+-----+----+    |
  |XXXX| LEFT JOIN XXXX|    |
  |XXXX+----+-----+----+    |
  |XXXXXXXXX|     |         |
  +---------+     +---------+
  (all of A + matching B)
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;

结果:

+----------+-------------+
| name     | dept_name   |
+----------+-------------+
| Alice    | Engineering |
| Bob      | Marketing   |
| Charlie  | Engineering |
| Diana    | NULL        |
+----------+-------------+
-- Diana included with NULL dept_name

当你需要左表的所有记录时使用,不管它们是否有匹配。

RIGHT JOIN(右外连接)

返回右表的所有行和左表的匹配行。如果没有匹配,左表列返回 NULL。

    Table A         Table B
  +---------+     +---------+
  |         |     |XXXXXXXXX|
  |    +----+-----+XXXX+XXXX|
  |    |XXXX RIGHT JOINXXXX|
  |    +----+-----+XXXX+XXXX|
  |         |     |XXXXXXXXX|
  +---------+     +---------+
  (matching A + all of B)
SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.id;

结果:

+----------+-------------+
| name     | dept_name   |
+----------+-------------+
| Alice    | Engineering |
| Charlie  | Engineering |
| Bob      | Marketing   |
| NULL     | Sales       |
+----------+-------------+
-- Sales included with NULL name

当你需要右表的所有记录时使用。

FULL OUTER JOIN(全外连接)

返回两张表的所有行。没有匹配的地方用 NULL 填充。

    Table A         Table B
  +---------+     +---------+
  |XXXXXXXXX|     |XXXXXXXXX|
  |XXXX+----+-----+XXXX+XXXX|
  |XXXX|FULL OUTER JOINXXXX|
  |XXXX+----+-----+XXXX+XXXX|
  |XXXXXXXXX|     |XXXXXXXXX|
  +---------+     +---------+
  (everything from both)
SELECT e.name, d.dept_name
FROM employees e
FULL OUTER JOIN departments d ON e.department_id = d.id;

结果:

+----------+-------------+
| name     | dept_name   |
+----------+-------------+
| Alice    | Engineering |
| Bob      | Marketing   |
| Charlie  | Engineering |
| Diana    | NULL        |
| NULL     | Sales       |
+----------+-------------+

当你需要两张表的所有记录,包括不匹配的行时使用。

CROSS JOIN(交叉连接)

返回两张表的笛卡尔积——左表的每一行与右表的每一行组合。不需要 ON 子句。

SELECT e.name, d.dept_name
FROM employees e
CROSS JOIN departments d;

-- Returns 4 × 3 = 12 rows

结果:

+----------+-------------+
| name     | dept_name   |
+----------+-------------+
| Alice    | Engineering |
| Alice    | Marketing   |
| Alice    | Sales       |
| Bob      | Engineering |
| Bob      | Marketing   |
| Bob      | Sales       |
| Charlie  | Engineering |
| Charlie  | Marketing   |
| Charlie  | Sales       |
| Diana    | Engineering |
| Diana    | Marketing   |
| Diana    | Sales       |
+----------+-------------+

用于生成组合、测试数据或日历网格。避免在大表上使用。

自连接(Self JOIN)

表与自身连接。适用于层级数据或同一表内的行比较。

-- employees_v2 with manager_id column
+----+---------+------------+
| id | name    | manager_id |
+----+---------+------------+
| 1  | Alice   | NULL       |
| 2  | Bob     | 1          |
| 3  | Charlie | 1          |
| 4  | Diana   | 2          |
+----+---------+------------+

SELECT
  e.name AS employee,
  m.name AS manager
FROM employees_v2 e
LEFT JOIN employees_v2 m ON e.manager_id = m.id;

结果:

+----------+---------+
| employee | manager |
+----------+---------+
| Alice    | NULL    |
| Bob      | Alice   |
| Charlie  | Alice   |
| Diana    | Bob     |
+----------+---------+

用于员工-经理关系、层级分类或行间比较。

用哪种连接?速查表

连接类型返回内容最佳使用场景
INNER JOINA ∩ B只要匹配数据
LEFT JOINA + (A ∩ B)所有左表 + 匹配右表
RIGHT JOIN(A ∩ B) + B所有右表 + 匹配左表
FULL OUTER JOINA ∪ B两表所有数据
CROSS JOINA × B生成所有组合
Self JOIN取决于连接类型层级/自引用数据

性能优化技巧

  • 始终为 JOIN 列创建索引——这是最大的性能提升。
  • 生产环境避免 SELECT *,只查询需要的列。
  • 尽量将小表放在 FROM 子句前面(虽然大多数优化器会自动处理)。
  • 使用 EXPLAIN ANALYZE 检查查询执行计划。
  • 对关联子查询优先使用 EXISTS 而不是 IN——它在第一次匹配时就会停止。
  • 避免在表达式或函数上连接(如 JOIN ON LOWER(a.name) = LOWER(b.name))——无法使用索引。
-- Create index on JOIN column
CREATE INDEX idx_emp_dept ON employees(department_id);

-- Use EXPLAIN to check query plan
EXPLAIN ANALYZE
SELECT e.name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;

常见错误

错误问题修复方法
使用 SELECT *返回不必要的列,浪费带宽和内存只选择需要的列
缺少 JOIN 列索引全表扫描导致查询极慢CREATE INDEX
NULL 比较陷阱NULL = NULL 结果为 FALSE使用 IS NULL 或 COALESCE
笛卡尔积意外缺少 ON 条件导致行数爆炸始终检查 ON 子句
在 WHERE 中过滤 OUTER JOINWHERE 过滤会把 LEFT JOIN 变成 INNER JOIN将条件放在 ON 子句中
多余的 DISTINCT掩盖了错误的 JOIN 逻辑修复 JOIN 条件而非加 DISTINCT
在函数上 JOIN索引无法使用,强制全表扫描存储规范化数据或使用计算列
-- ❌ Wrong: WHERE turns LEFT JOIN into INNER JOIN
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id
WHERE d.dept_name = 'Engineering';   -- filters out NULLs!

-- ✅ Correct: Put condition in ON clause
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d
  ON e.department_id = d.id
  AND d.dept_name = 'Engineering';   -- preserves NULLs

常见问题

INNER JOIN 和 OUTER JOIN 有什么区别?

INNER JOIN 只返回两张表中匹配的行。OUTER JOIN(LEFT、RIGHT 或 FULL)还包含一个或两个表中不匹配的行,用 NULL 填充缺失值。

可以连接两张以上的表吗?

可以。你可以链式使用多个 JOIN。例如:SELECT * FROM A JOIN B ON A.id = B.a_id JOIN C ON B.id = C.b_id。没有实际限制。

INNER JOIN 忘记写 ON 子句会怎样?

在大多数 SQL 方言中,INNER JOIN 缺少 ON 子句会导致语法错误。在某些方言中,可能产生 CROSS JOIN(笛卡尔积),返回大量行。

LEFT JOIN 和 LEFT OUTER JOIN 一样吗?

是的。OUTER 关键字是可选的。在所有主流 SQL 数据库中,LEFT JOIN 和 LEFT OUTER JOIN 完全相同。

哪种连接最快?

INNER JOIN 通常最快,因为它只处理匹配的行。但实际性能取决于索引、表大小和查询优化器。始终使用 EXPLAIN 测量。

什么是 NATURAL JOIN?

NATURAL JOIN 自动在两个表中同名的列上进行连接。生产环境中通常不建议使用,因为添加列可能会悄悄改变连接行为。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

SQLSQL FormatterPSSQL to Prisma Schema{ }JSON Formatter

相关文章

JSON vs YAML vs TOML:你应该用哪种配置格式?

比较 JSON、YAML 和 TOML 配置格式,了解语法、特性和优缺点,选择适合你项目的格式。

Prisma Schema 与关联关系指南

掌握 Prisma Schema 设计:模型、关联关系(1:1、1:N、M:N)、枚举、索引和迁移。常见数据库模式的实用示例。