SQL02-连接全解:从基础到实战

深入讲解 SQL 连接的各种类型,包括内连接、外连接、自连接、复合连接等,配合实例代码和图解,助你轻松掌握表连接技巧。

引言

在关系型数据库中,数据通常被分散存储在多个表中,以遵循规范化设计原则。连接(JOIN) 是 SQL 中最强大的功能之一,它允许我们从多个表中组合相关数据。

本文将系统讲解 SQL 连接的 12 种用法,帮助你从零掌握表连接的核心技巧。


连接类型速览

连接类型 关键字 说明 图示
内连接 INNER JOIN 只返回匹配的行 A ∩ B
左外连接 LEFT JOIN 返回左表全部 + 匹配的右表 A
右外连接 RIGHT JOIN 返回右表全部 + 匹配的左表 B
交叉连接 CROSS JOIN 返回笛卡尔积 A × B
自连接 表别名 表与自身连接

一、内连接(INNER JOIN)

基本概念

内连接是最常用的连接类型,只返回两个表中满足连接条件的行

  graph LR
    A[表A] --- B[表B]
    C[内连接结果 = 交集]

语法示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-- 查询订单明细及对应产品信息
SELECT
    oi.order_id,
    oi.product_id,
    p.name AS product_name,
    oi.quantity,
    oi.unit_price
FROM order_items oi
INNER JOIN products p
    ON p.product_id = oi.product_id;

执行逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
┌─────────────────┐         ┌─────────────────┐
│   order_items   │         │    products     │
├─────────────────┤         ├─────────────────┤
│ order_id: 1     │────┐    │ product_id: 5   │
│ product_id: 5   │    ├────│ name: ' Laptop' │
│ quantity: 2     │    │    │ price: 999.99   │
└─────────────────┘    │    └─────────────────┘
        ┌──────────────────────────┐
        │       结果集              │
        ├──────────────────────────┤
        │ order_id: 1              │
        │ product_name: 'Laptop'   │
        │ quantity: 2              │
        │ unit_price: 999.99       │
        └──────────────────────────┘

💡 注意:当 product_id 不匹配时,该行不会出现在结果中。


二、跨数据库连接

使用场景

企业级应用中,数据可能分散在不同数据库中。SQL 允许跨数据库连接表。

1
2
3
4
5
-- 连接当前数据库和 sql_inventory 数据库
SELECT *
FROM order_items oi
JOIN sql_inventory.products p
    ON oi.product_id = p.product_id;

语法格式

1
2
3
database_name.table_name.column_name
     │          │         │
   数据库名    表名     列名

最佳实践

建议 说明
✅ 明确指定数据库 避免歧义,提高可读性
✅ 使用同义词 为跨库表创建别名简化查询
⚠️ 注意性能 跨库查询可能较慢

三、自连接(SELF JOIN)

基本概念

自连接是指表与自身进行连接,常用于处理层次结构数据。

1
2
3
4
5
6
7
8
-- 查询员工及其经理信息
SELECT
    e.employee_id,
    e.first_name AS employee,
    m.first_name AS manager
FROM employees e
LEFT JOIN employees m
    ON e.reports_to = m.employee_id;

应用场景

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────┐
│              employees 表                │
├──────┬────────────┬──────────────┐      │
│  ID  │   name     │ reports_to   │      │
├──────┼────────────┼──────────────┤      │
│   1  │  Alice     │     NULL     │ ← CEO│
│   2  │  Bob       │      1       │      │
│   3  │  Carol     │      1       │      │
│   4  │  Dave      │      2       │      │
└──────┴────────────┴──────────────┘      │
              ↓                             │
      自连接查询结果:                      │
┌──────┬────────────┬────────────┐         │
│  ID  │  employee  │  manager    │         │
├──────┼────────────┼────────────┤         │
│   1  │  Alice     │   NULL     │         │
│   2  │  Bob       │  Alice     │         │
│   3  │  Carol     │  Alice     │         │
│   4  │  Dave      │   Bob      │         │
└──────┴────────────┴────────────┘         │
└─────────────────────────────────────────┘

常见用例

场景 示例
组织架构 员工 → 经理
分类层级 子分类 → 父分类
地区数据 城市 → 省份 → 国家
评论系统 评论 → 父评论

四、多表连接

语法示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- 查询支付详情(客户 + 支付方式)
SELECT
    p.date,
    p.invoice_id,
    c.name AS client,
    c.phone,
    pm.name AS payment_method
FROM payments p
JOIN clients c
    ON p.client_id = c.client_id
JOIN payment_methods pm
    ON p.payment_method = pm.payment_method_id;

连接顺序

  graph TD
    A[payments 主表] --> B[clients 客户表]
    A --> C[payment_methods 支付方式表]
    B --> D[结果集]
    C --> D

性能建议

⚠️ 注意:连接的表越多,查询性能影响越大。建议:

  • 限制连接数量(通常不超过 5 个表)
  • 确保连接字段有索引
  • 将小表作为驱动表

五、复合连接

基本概念

复合连接使用多个列作为连接条件,常见于复合主键的场景。

1
2
3
4
5
6
-- 订单项和订单项备注通过订单ID和产品ID连接
SELECT *
FROM order_items oi
JOIN order_item_notes oin
    ON oi.order_id = oin.order_id
    AND oi.product_id = oin.product_id;

场景说明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
┌─────────────────────────┐      ┌─────────────────────────┐
│      order_items        │      │   order_item_notes      │
├──────────┬──────────────┤      ├──────────┬──────────────┤
│ order_id │ product_id   │      │ order_id │ product_id   │
├──────────┼──────────────┤      ├──────────┼──────────────┤
│    1     │     5        │─────►│    1     │     5        │
│    1     │     8        │      │    1     │     8        │
│    2     │     5        │      │    2     │     5        │
└──────────┴──────────────┘      └──────────┴──────────────┘
         复合主键                    复合主键
    (order_id, product_id)      (order_id, product_id)

六、隐式连接 vs 显式连接

隐式连接(传统语法)

1
2
3
SELECT *
FROM customers c, orders o
WHERE c.customer_id = o.customer_id;

显式连接(推荐)

1
2
3
4
SELECT *
FROM customers c
INNER JOIN orders o
    ON c.customer_id = o.customer_id;

对比分析

特性 隐式连接 显式连接
可读性 ⭐⭐ ⭐⭐⭐⭐⭐
连接条件分离 ❌ 容易与过滤条件混淆 ON 子句明确
外连接支持 ❌ 不支持 ✅ 完整支持
标准 SQL ⚠️ 旧语法 ✅ ANSI 标准

💡 最佳实践:始终使用显式连接语法(INNER JOIN/LEFT JOIN


七、外连接(OUTER JOIN)

类型对比

  graph LR
    subgraph 左表 A
    A1[1]
    A2[2]
    A3[3]
    end
    subgraph 右表 B
    B1[2]
    B2[4]
    end
连接类型 结果 说明
INNER JOIN {2} 只保留匹配
LEFT JOIN {1,2,3} 保留左表全部
RIGHT JOIN {2,4} 保留右表全部
FULL JOIN {1,2,3,4} 保留全部(MySQL 不支持)

左外连接示例

1
2
3
4
5
6
7
8
-- 查询所有产品及其订单数量(包括无订单的产品)
SELECT
    p.product_id,
    p.name,
    oi.quantity
FROM products p
LEFT JOIN order_items oi
    ON p.product_id = oi.product_id;

结果特征: 无订单的产品,quantity 显示为 NULL


八、多表外连接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
-- 查询订单完整信息(客户 + 运输商 + 状态)
SELECT
    o.order_date,
    o.order_id,
    c.first_name AS customer,
    s.name AS shipper,
    os.name AS status
FROM orders o
LEFT JOIN shippers s
    ON o.shipper_id = s.shipper_id
LEFT JOIN order_statuses os
    ON o.status = os.order_status_id
LEFT JOIN customers c
    ON o.customer_id = c.customer_id
ORDER BY os.name;

连接策略

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
        ┌─────────────────────────────────┐
        │            orders               │ ← 主表
        │     (所有订单都会保留)           │
        └─────────────┬───────────────────┘
         ┌────────────┼────────────┐
         ▼            ▼            ▼
    ┌─────────┐ ┌─────────┐ ┌─────────┐
    │shippers │ │statuses │ │customers│
    └─────────┘ └─────────┘ └─────────┘

九、自外连接

1
2
3
4
5
6
7
8
-- 查询所有员工及经理(包括没有经理的CEO)
SELECT
    e.employee_id,
    e.first_name,
    m.first_name AS manager
FROM employees e
LEFT JOIN employees m
    ON e.reports_to = m.employee_id;

结果特点: CEO 的 manager 字段为 NULL


十、USING 子句

简化连接条件

当两个表的列名相同时,可以使用 USING 简化语法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 传统写法
SELECT *
FROM payments p
JOIN clients c
    ON p.client_id = c.client_id;

-- USING 写法(更简洁)
SELECT *
FROM payments p
JOIN clients c
    USING(client_id);

多列使用

1
2
3
4
5
-- 复合连接的 USING 写法
SELECT *
FROM order_items oi
JOIN order_item_notes oin
    USING(order_id, product_id);

语法对比

写法 适用场景
ON t1.id = t2.id 列名不同
USING(id) 列名相同
NATURAL JOIN 所有同名列(不推荐)

⚠️ 注意USING 后的列名在结果集中只出现一次


十一、交叉连接(CROSS JOIN)

基本概念

交叉连接返回笛卡尔积——两个表每一行的所有可能组合。

1
2
3
4
5
6
-- 两种写法等价
SELECT * FROM shippers s, products p;

SELECT *
FROM shippers s
CROSS JOIN products p;

结果示例

1
2
3
4
shippers 表有 3 行
products 表有 10 行

结果集:3 × 10 = 30 行

实际应用场景

场景 说明
生成所有组合 产品 × 颜色 × 尺寸
测试数据生成 快速构造测试集
日历生成 日期 × 时间段

⚠️ 警告:交叉连接可能产生海量数据,使用前务必评估数据量!


十二、联合(UNION)

基本概念

UNION 将多个 SELECT 语句的结果合并为一个结果集

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-- 客户分级查询
SELECT
    customer_id,
    first_name,
    points,
    'Bronze' AS type
FROM customers
WHERE points < 2000

UNION

SELECT
    customer_id,
    first_name,
    points,
    'Silver' AS type
FROM customers
WHERE points BETWEEN 2000 AND 3000

UNION

SELECT
    customer_id,
    first_name,
    points,
    'Gold' AS type
FROM customers
WHERE points >= 3000

ORDER BY first_name;

UNION vs UNION ALL

特性 UNION UNION ALL
去重 ✅ 自动去重 ❌ 保留全部
性能 较慢(需要排序去重) 较快
使用场景 需要去重 确定无重复/需保留

使用规则

1
2
3
4
✓ 各 SELECT 语句的列数必须相同
✓ 对应列的数据类型必须兼容
✓ 结果集的列名取自第一个 SELECT
✓ ORDER BY 应放在最后一条 SELECT 之后

连接性能优化指南

1. 索引策略

1
2
3
-- 为连接条件创建索引
CREATE INDEX idx_order_items_product ON order_items(product_id);
CREATE INDEX idx_products_id ON products(product_id);

2. 小表驱动大表

1
2
3
-- 推荐:小表在前
FROM small_table s
JOIN large_table l ON s.id = l.small_id

3. 避免无效连接

1
2
3
4
5
6
7
8
9
-- ❌ 不推荐:在 WHERE 中过滤
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE c.active = 1

-- ✅ 推荐:在 JOIN 前过滤
FROM orders o
JOIN (SELECT * FROM customers WHERE active = 1) c
    ON o.customer_id = c.customer_id

4. 选择性原则

优先级 连接类型 原因
1 INNER JOIN 结果集最小
2 LEFT/RIGHT JOIN 单侧保留
3 CROSS JOIN 结果集最大

常见问题排查

问题 1:连接结果不符合预期

1
2
3
-- 检查连接条件是否正确
-- 检查是否有 NULL 值影响连接
SELECT COUNT(*) FROM table WHERE join_column IS NULL;

问题 2:性能慢

1
2
3
4
5
-- 使用 EXPLAIN 分析执行计划
EXPLAIN SELECT * FROM table1 JOIN table2 ON ...;

-- 检查是否使用了索引
SHOW INDEX FROM table_name;

问题 3:列名冲突

1
2
3
4
5
6
7
8
-- 使用表别名或完整列名
SELECT o.order_id, c.order_id  -- ❌ 歧义
FROM orders o
JOIN customers c ON ...

SELECT o.order_id, c.customer_id  -- ✅ 明确
FROM orders o
JOIN customers c ON ...

总结

连接类型决策树

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
需要组合多表数据?
    ├─ 需要所有匹配行?
    │    └─ 使用 INNER JOIN
    ├─ 需要保留某个表全部行?
    │    ├─ 左表 → LEFT JOIN
    │    └─ 右表 → RIGHT JOIN
    ├─ 表与自身关联?
    │    └─ 使用 SELF JOIN(表别名)
    ├─ 需要所有组合?
    │    └─ 使用 CROSS JOIN(谨慎!)
    └─ 需要合并结果集?
         └─ 使用 UNION / UNION ALL

快速参考表

连接类型 关键字 行为
内连接 INNER JOIN 只返回匹配
左连接 LEFT JOIN 左表全 + 匹配右表
右连接 RIGHT JOIN 右表全 + 匹配左表
交叉连接 CROSS JOIN 笛卡尔积
自连接 表别名 表自身连接
自然连接 NATURAL JOIN 自动匹配同名列
联合 UNION 合并结果集

下一步学习


💬 有疑问? 欢迎在评论区讨论,或提交 Issue 反馈问题。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计