引言
在关系型数据库中,数据通常被分散存储在多个表中,以遵循规范化设计原则。连接(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 反馈问题。