⚡ 编程实验室🏗️ HTML🎨 CSS⚡ JavaScript🐍 Python🗄️ SQL☕ Java⚛️ React💚 Vue🟢 Node.js⚙️ C语言🐘 PHP🐹 Go🔷 TypeScript🐬 MySQL🔧 C++🎯 C#🦀 Rust🅱️ Bootstrap💡 jQuery🎸 Django🍃 MongoDB👗 Sass🎪 Kotlin📊 R语言📋 XML📊 Excel🐘 PostgreSQL🐳 Docker🅰️ Angular🎮 游戏🏠 网站首页

SQL JOIN 多表联查

学习 INNER JOIN 连接多张表 · 难度:进阶 · +15XP

SQL JOIN 多表联查 — 打通数据孤岛

一、什么是 JOIN?为什么它是关系型数据库的精髓?

JOIN(连接)是 SQL 中最强大的功能之一,用于根据关联条件多张表的数据合并成一张结果集。在关系型数据库设计中,数据通常分散在多个表中(这叫规范化 Normalization),以避免数据冗余。例如:用户信息存在 users 表,订单信息存在 orders 表,需要通过用户 ID 将两张表关联起来才能看到"谁买了什么"。

为什么 JOIN 如此重要?在实际业务中,很少有查询只涉及一张表。用户的订单历史、商品的分类信息、文章的作者资料……几乎所有有意义的查询都需要 JOIN。掌握 JOIN 是 SQL 能力的分水岭:不会 JOIN 只能做单表的 CRUD,会 JOIN 才能真正挖掘数据的价值。

JOIN 的本质是把两张(或更多)表按照指定的条件进行"横向拼接"。最常见的条件是两个表中相等的字段(如 users.id = orders.user_id),但也可以用不等条件、范围条件等进行连接。

二、六种 JOIN 类型一图看懂


INNER JOIN:           LEFT JOIN:            RIGHT JOIN:
┌───┬───┐  ┌───┬───┐  ┌───┬───┐  ┌───┬───┐  ┌───┬───┐  ┌───┬───┐
│ A │ B │  │ B │ C │  │ A │ B │  │ B │ C │  │ A │ B │  │ B │ C │
├───┼───┤  ├───┼───┤  ├───┼───┤  ├───┼───┤  ├───┼───┤  ├───┼───┤
│ 1 │ a │  │ a │ x │  │ 1 │ a │  │ a │ x │  │ 1 │ a │  │ a │ x │
│ 2 │ b │  │ b │ y │  │ 2 │ b │  │ b │ y │  │ 2 │ b │  │ b │ y │
│ 3 │ c │  │ d │ z │  │ 3 │ c │  │ d │ z │  │ 3 │ c │  │ d │ z │
└───┴───┘  └───┴───┘  └───┴───┘  └───┴───┘  └───┴───┘  └───┴───┘
   结果: 1-a-x                结果: 1-a-x                结果: 1-a-x
         2-b-y                      2-b-y                      2-b-y
                                   3-c-NULL                   NULL-d-z
(只有匹配的)            (左表全部保留)          (右表全部保留)

FULL OUTER JOIN: CROSS JOIN: SELF JOIN: 结果: 1-a-x 结果: 1-a-x 同一张表自己连自己 2-b-y 1-a-z (用不同别名区分) 3-c-NULL 2-b-x NULL-d-z 2-b-y 3-c-x (两表全部保留) 3-c-z (笛卡尔积)

三、七种 JOIN 对比速查表

JOIN 类型保留哪些行不匹配时典型场景
INNER JOIN两表都匹配丢弃不匹配的行查询有订单的用户
LEFT JOIN左表全部 + 右表匹配右表字段为 NULL所有用户及其订单(含无订单用户)
RIGHT JOIN右表全部 + 左表匹配左表字段为 NULL所有订单及其用户(含无主订单)
FULL OUTER JOIN两表全部不匹配侧为 NULL两表数据完整对比(MySQL 不直接支持)
CROSS JOIN笛卡尔积(每行 × 每行)无匹配概念生成组合(如颜色 × 尺码)
SELF JOIN表自己连自己同 INNER/LEFT员工-经理关系、树形结构
NATURAL JOIN自动按同名列匹配不推荐(隐式行为,易出错)

四、详细代码示例(逐行注释)

-- ======== 准备示例数据 ========
-- users 表:用户
-- | id | name    |
-- | 1  | 小明    |
-- | 2  | 小红    |
-- | 3  | 小刚    |
--
-- orders 表:订单
-- | id | user_id | product  | amount |
-- | 1  | 1       | 手机     | 3999   |
-- | 2  | 1       | 电脑     | 6999   |
-- | 3  | 2       | 耳机     | 299    |
-- | 4  | 5       | 键盘     | 499    |(user_id=5 不存在于 users 表)

-- ======== 1. INNER JOIN(内连接)— 仅返回两表匹配的行 ======== -- 查询每个订单属于哪个用户(丢弃没有订单的用户和没有用户的订单) SELECT u.name AS 用户名, o.product AS 商品, o.amount AS 金额 FROM users AS u -- u 是 users 的别名 INNER JOIN orders AS o -- o 是 orders 的别名 ON u.id = o.user_id; -- 连接条件 -- 结果:小明-手机, 小明-电脑, 小红-耳机 -- 小刚没有订单 → 不出现,键盘订单无对应用户 → 不出现

-- 写法等价(隐式内连接,不推荐) SELECT u.name, o.product FROM users u, orders o WHERE u.id = o.user_id;

-- ======== 2. LEFT JOIN(左连接)— 保留左表全部行 ======== -- 查询所有用户及其订单(没有订单的用户也要显示) SELECT u.name AS 用户名, o.product AS 商品, o.amount AS 金额 FROM users AS u LEFT JOIN orders AS o ON u.id = o.user_id; -- 结果:小明-手机, 小明-电脑, 小红-耳机, 小刚-NULL -- 小刚没有订单 → 保留,订单字段为 NULL

-- 常用:找出没有订单的用户 SELECT u.name AS 用户名 FROM users AS u LEFT JOIN orders AS o ON u.id = o.user_id WHERE o.id IS NULL; -- 订单表主键为 NULL = 没有订单 -- 结果:小刚

-- ======== 3. RIGHT JOIN(右连接)— 保留右表全部行 ======== -- 查询所有订单及其用户(包括孤立的订单) SELECT u.name AS 用户名, o.product AS 商品 FROM users AS u RIGHT JOIN orders AS o ON u.id = o.user_id; -- 结果:小明-手机, 小明-电脑, 小红-耳机, NULL-键盘(键盘订单无对应用户) -- 实际中可以用 LEFT JOIN 代替(交换表顺序即可),RIGHT JOIN 不常用

-- ======== 4. FULL OUTER JOIN(全外连接)— 保留两表全部行 ======== -- MySQL 不直接支持 FULL OUTER JOIN,用 UNION 模拟 SELECT u.name AS 用户名, o.product AS 商品 FROM users AS u LEFT JOIN orders AS o ON u.id = o.user_id UNION SELECT u.name AS 用户名, o.product AS 商品 FROM users AS u RIGHT JOIN orders AS o ON u.id = o.user_id; -- 结果:小明-手机, 小明-电脑, 小红-耳机, 小刚-NULL, NULL-键盘

-- ======== 5. CROSS JOIN(交叉连接)— 笛卡尔积 ======== -- 生成所有可能的组合 SELECT u.name AS 用户, o.product AS 商品 FROM users AS u CROSS JOIN orders AS o; -- 3 个用户 × 4 个订单 = 12 行结果(每种组合都出现) -- 实用场景:生成 SKU 组合 -- 如:颜色表有 3 种颜色,尺码表有 4 种尺码,CROSS JOIN 生成 12 个 SKU

-- ======== 6. SELF JOIN(自连接)— 表自己连接自己 ======== -- 示例:员工表 employees (id, name, manager_id) -- 查询每个员工及其直属经理 SELECT e.name AS 员工, m.name AS 经理 FROM employees AS e -- e 代表员工 LEFT JOIN employees AS m -- m 代表经理(同一张表的另一个别名) ON e.manager_id = m.id; -- 员工的经理 ID = 经理的员工 ID

-- ======== 7. 多表 JOIN(3 张表以上) ======== -- 查询用户的订单详情 SELECT u.name AS 用户名, o.product AS 商品, o.amount AS 金额, c.name AS 商品分类 FROM users AS u LEFT JOIN orders AS o ON u.id = o.user_id LEFT JOIN categories AS c ON o.category_id = c.id -- 继续连接第三张表 ORDER BY o.amount DESC;

-- ======== 8. JOIN + 聚合函数 ======== -- 查询每个用户的订单总额 SELECT u.name AS 用户名, COUNT(o.id) AS 订单数, -- 统计订单数量 COALESCE(SUM(o.amount), 0) AS 总消费 -- 总额(NULL 转为 0) FROM users AS u LEFT JOIN orders AS o ON u.id = o.user_id GROUP BY u.id, u.name; -- 按用户分组

-- ======== 9. 复合条件的 JOIN ======== -- ON 条件不只是等值,可以包含多个条件和不等条件 SELECT u.name, o.product FROM users AS u JOIN orders AS o ON u.id = o.user_id AND o.amount > 500; -- 只要金额大于 500 的订单 -- 等价于:JOIN + WHERE o.amount > 500

-- ======== 10. JOIN 查询优化建议 ======== -- 说明:解释 EXPLAIN 输出,关注 type、key、rows 字段 -- EXPLAIN SELECT ... -- type 从好到差:system > const > eq_ref > ref > range > index > ALL -- 确保 JOIN 的 ON 条件列都有索引! -- 为 orders.user_id 创建索引可以大幅提升 JOIN 性能

五、INNER JOIN vs LEFT JOIN — 核心区别

场景INNER JOINLEFT JOIN
查询有订单的用户INNER JOIN(天然过滤)LEFT JOIN + WHERE o.id IS NOT NULL
查询所有用户(含无订单)做不到LEFT JOIN 直接实现
统计用户订单数只统计有订单的用户统计全部用户(含 0 单)
性能通常更快(数据量更小)可能更慢(数据量大)
使用频率高(约 60% 的场景)高(约 35% 的场景)

六、JOIN 常见错误与注意事项

错误后果正确做法
ON 条件遗漏笛卡尔积,返回大量错误数据总是显式写 ON 条件
连接列无索引大表 JOIN 极慢对 JOIN 的关联列建索引
多对多关联数据膨胀(行数异常增多)检查关联条件是否唯一
WHERE 过滤与 ON 混淆LEFT JOIN 后 WHERE 右表条件会将左连接变成内连接右表的过滤条件写在 ON 里
SELECT * 且列名冲突同名列被覆盖或混淆明确列出需要的列并加别名

七、实践任务

  1. 创建三张表:students(id, name, class_id)、classes(id, class_name)、scores(id, student_id, subject, score)。每张表插入至少 5 条数据
  2. 使用 INNER JOIN 查询每个学生的班级名称
  3. 使用 LEFT JOIN 查询所有学生及其成绩(包括没有成绩的学生)
  4. 使用三表 JOIN 查询每个学生的班级名称和各科成绩
  5. 统计每个班级的学生人数和平均成绩(使用 JOIN + GROUP BY)
  6. 使用 SELF JOIN:在员工表中查询每个员工的直属上级
Ctrl+Enter
🚀 升级VIP
解锁全部课程+AI助手

🏆 学习排行

加载中...

📊 统计

📖 146 篇
0 完成
🔥 0