数据库连接(JOIN)详解
一、连接类型总览
下图展示了主要的连接类型及其关系:
graph TD
A[JOIN连接类型] --> B[内连接 INNER JOIN]
A --> C[外连接 OUTER JOIN]
A --> D[交叉连接 CROSS JOIN]
A --> E[自连接 SELF JOIN]
C --> F[左外连接 LEFT JOIN]
C --> G[右外连接 RIGHT JOIN]
C --> H[全外连接 FULL JOIN]
F --> I[返回左表所有记录
+ 匹配的右表记录] G --> J[返回右表所有记录
+ 匹配的左表记录] H --> K[返回左右表所有记录
未匹配的显示NULL] B --> L[只返回两个表
匹配的记录] style I fill:#e1f5e1 style J fill:#e1f5e1 style K fill:#e1f5e1 style L fill:#e1f5e1
+ 匹配的右表记录] G --> J[返回右表所有记录
+ 匹配的左表记录] H --> K[返回左右表所有记录
未匹配的显示NULL] B --> L[只返回两个表
匹配的记录] style I fill:#e1f5e1 style J fill:#e1f5e1 style K fill:#e1f5e1 style L fill:#e1f5e1
二、连接类型详解(附图示)
1. 内连接 (INNER JOIN)
定义 :只返回两个表中匹配的行。
图示:
css
表A 表B
┌───┐ ┌───┐
│ 1 │ │ 1 │
│ 2 │ │ 3 │
│ 3 │ │ 4 │
└───┘ └───┘
INNER JOIN 结果:
┌───┬───┐
│ A │ B │
├───┼───┤
│ 1 │ 1 │
│ 3 │ 3 │
└───┴───┘
SQL语法:
sql
-- 标准写法
SELECT *
FROM 表A
INNER JOIN 表B ON 表A.id = 表B.a_id;
-- 简写(不推荐)
SELECT *
FROM 表A, 表B
WHERE 表A.id = 表B.a_id;
实际示例:
sql
-- 学生表和成绩表
SELECT s.student_id, s.name, g.course, g.score
FROM students s
INNER JOIN grades g ON s.student_id = g.student_id;
2. 左外连接 (LEFT JOIN / LEFT OUTER JOIN)
定义 :返回左表所有行 + 匹配的右表行。右表无匹配则显示NULL。
图示:
css
表A(主) 表B
┌───┐ ┌───┐
│ 1 │ │ 1 │
│ 2 │ │ 3 │
│ 3 │ │ 4 │
└───┘ └───┘
LEFT JOIN 结果:
┌───┬──────┐
│ A │ B │
├───┼──────┤
│ 1 │ 1 │
│ 2 │ NULL │ ← 表A的2在表B中无匹配
│ 3 │ 3 │
└───┴──────┘
SQL语法:
sql
SELECT *
FROM 表A
LEFT JOIN 表B ON 表A.id = 表B.a_id;
实际示例:
sql
-- 所有学生(包括没成绩的)及其成绩
SELECT s.student_id, s.name, g.course, g.score
FROM students s
LEFT JOIN grades g ON s.student_id = g.student_id;
-- 只查没有成绩的学生
SELECT s.student_id, s.name
FROM students s
LEFT JOIN grades g ON s.student_id = g.student_id
WHERE g.student_id IS NULL;
3. 右外连接 (RIGHT JOIN / RIGHT OUTER JOIN)
定义 :返回右表所有行 + 匹配的左表行。左表无匹配则显示NULL。
图示:
css
表A 表B(主)
┌───┐ ┌───┐
│ 1 │ │ 1 │
│ 2 │ │ 3 │
│ 3 │ │ 4 │
└───┘ └───┘
RIGHT JOIN 结果:
┌──────┬───┐
│ A │ B │
├──────┼───┤
│ 1 │ 1 │
│ 3 │ 3 │
│ NULL │ 4 │ ← 表B的4在表A中无匹配
└──────┴───┘
SQL语法:
sql
SELECT *
FROM 表A
RIGHT JOIN 表B ON 表A.id = 表B.a_id;
实际示例:
sql
-- 所有课程(包括没有学生选的)及其学生
SELECT c.course_id, c.course_name, s.name
FROM students s
RIGHT JOIN courses c ON s.course_id = c.course_id;
4. 全外连接 (FULL JOIN / FULL OUTER JOIN)
定义 :返回两个表的所有行。匹配的显示数据,不匹配的显示NULL。
图示:
css
表A 表B
┌───┐ ┌───┐
│ 1 │ │ 1 │
│ 2 │ │ 3 │
│ 3 │ │ 4 │
└───┘ └───┘
FULL JOIN 结果:
┌──────┬──────┐
│ A │ B │
├──────┼──────┤
│ 1 │ 1 │
│ 2 │ NULL │ ← 只在表A中存在
│ 3 │ 3 │
│ NULL │ 4 │ ← 只在表B中存在
└──────┴──────┘
SQL语法:
sql
-- MySQL不支持FULL JOIN,需用UNION模拟
SELECT *
FROM 表A
LEFT JOIN 表B ON 表A.id = 表B.a_id
UNION
SELECT *
FROM 表A
RIGHT JOIN 表B ON 表A.id = 表B.a_id
WHERE 表A.id IS NULL;
-- PostgreSQL、SQL Server等支持
SELECT *
FROM 表A
FULL OUTER JOIN 表B ON 表A.id = 表B.a_id;
5. 交叉连接 (CROSS JOIN)
定义 :返回两个表的笛卡尔积(所有可能的组合)。
图示:
css
表A 表B
┌───┐ ┌───┐
│ 1 │ │ A │
│ 2 │ │ B │
└───┘ └───┘
CROSS JOIN 结果:
┌───┬───┐
│ A │ B │
├───┼───┤
│ 1 │ A │
│ 1 │ B │
│ 2 │ A │
│ 2 │ B │ ← 2×2=4行
└───┴───┘
SQL语法:
sql
-- 显式写法
SELECT *
FROM 表A
CROSS JOIN 表B;
-- 隐式写法
SELECT *
FROM 表A, 表B;
实际示例:
sql
-- 生成所有日期和产品的组合
SELECT d.date, p.product_name
FROM dates d
CROSS JOIN products p;
6. 自连接 (SELF JOIN)
定义:表与自身连接,用于查询层级关系。
实际示例:
sql
-- 员工表(每个员工有经理)
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
manager_id INT
);
-- 查询员工及其经理
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
-- 结果示例:
-- employee | manager
-- ----------|---------
-- 张三 | 李四
-- 王五 | 李四
-- 李四 | NULL
三、连接条件详解
1. ON vs USING
sql
-- ON:指定连接条件
SELECT *
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;
-- USING:当列名相同时可简写(仅某些数据库支持)
SELECT *
FROM orders
JOIN customers USING (customer_id);
2. 多条件连接
sql
-- 多个连接条件
SELECT *
FROM table1 t1
JOIN table2 t2 ON t1.id = t2.id
AND t1.date = t2.date
AND t1.status = 'active';
3. 连接多个表
sql
SELECT
s.name AS student,
c.course_name,
g.score,
t.name AS teacher
FROM students s
JOIN grades g ON s.student_id = g.student_id
JOIN courses c ON g.course_id = c.course_id
LEFT JOIN teachers t ON c.teacher_id = t.teacher_id;
四、性能优化与最佳实践
1. 连接顺序优化
sql
-- 小表在前,大表在后(某些优化器会自动优化)
SELECT *
FROM small_table s -- 假设100行
JOIN large_table l -- 假设1,000,000行
ON s.id = l.s_id;
2. 使用索引
sql
-- 在连接列上创建索引
CREATE INDEX idx_student_id ON grades(student_id);
CREATE INDEX idx_course_id ON grades(course_id);
-- 查询时会使用索引加速
EXPLAIN SELECT *
FROM students s
JOIN grades g ON s.student_id = g.student_id;
3. **避免SELECT ***
sql
-- 不好的写法
SELECT *
FROM table1
JOIN table2 ON ...
-- 好的写法:只选择需要的列
SELECT
t1.id,
t1.name,
t2.category,
t2.price
FROM table1 t1
JOIN table2 t2 ON t1.id = t2.t1_id;
4. 处理NULL值
sql
-- 使用COALESCE处理NULL
SELECT
s.name,
COALESCE(g.score, 0) AS score, -- 如果NULL显示为0
CASE
WHEN g.score IS NULL THEN '缺考'
WHEN g.score >= 60 THEN '及格'
ELSE '不及格'
END AS status
FROM students s
LEFT JOIN grades g ON s.student_id = g.student_id;
五、不同数据库的差异
| 连接类型 | MySQL | PostgreSQL | SQL Server | Oracle |
|---|---|---|---|---|
| INNER JOIN | ✓ | ✓ | ✓ | ✓ |
| LEFT JOIN | ✓ | ✓ | ✓ | ✓ |
| RIGHT JOIN | ✓ | ✓ | ✓ | ✓ |
| FULL JOIN | ✗(需模拟) | ✓ | ✓ | ✓ |
| CROSS JOIN | ✓ | ✓ | ✓ | ✓ |
| NATURAL JOIN | ✓ | ✓ | ✓ | ✓ |
六、实用示例集合
示例1:电商系统查询
sql
-- 查询所有订单详情(包括未发货的)
SELECT
o.order_id,
o.order_date,
c.customer_name,
p.product_name,
oi.quantity,
oi.price,
s.status_name
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
LEFT JOIN order_items oi ON o.order_id = oi.order_id
LEFT JOIN products p ON oi.product_id = p.product_id
LEFT JOIN shipping_status s ON o.shipping_id = s.status_id;
示例2:社交网络好友关系
sql
-- 查询用户A的所有好友(双向关系)
SELECT
u.name AS user_name,
f.name AS friend_name,
r.relationship_type
FROM users u
JOIN friendships fs ON u.user_id = fs.user1_id
JOIN users f ON fs.user2_id = f.user_id
LEFT JOIN relationship_types r ON fs.relationship_id = r.type_id
WHERE u.user_id = 1
UNION
SELECT
u.name AS user_name,
f.name AS friend_name,
r.relationship_type
FROM users u
JOIN friendships fs ON u.user_id = fs.user2_id
JOIN users f ON fs.user1_id = f.user_id
LEFT JOIN relationship_types r ON fs.relationship_id = r.type_id
WHERE u.user_id = 1;
七、调试技巧
1. 逐步构建复杂连接
sql
-- 第一步:先测试一个连接
SELECT COUNT(*) FROM table1 t1
JOIN table2 t2 ON t1.id = t2.t1_id;
-- 第二步:添加更多连接
SELECT COUNT(*) FROM table1 t1
JOIN table2 t2 ON t1.id = t2.t1_id
JOIN table3 t3 ON t2.id = t3.t2_id;
-- 第三步:添加WHERE条件和选择列
2. 使用EXPLAIN分析
sql
-- 查看查询执行计划
EXPLAIN
SELECT *
FROM students s
JOIN grades g ON s.student_id = g.student_id;
总结
- INNER JOIN:要交集数据时使用
- LEFT JOIN:要左表全部+右表匹配时使用
- RIGHT JOIN:要右表全部+左表匹配时使用(可转换为LEFT JOIN)
- FULL JOIN:要两个表所有数据时使用
- CROSS JOIN:要所有组合时使用(谨慎使用)
- 自连接:处理层级关系时使用
记忆口诀:
- LEFT JOIN:左表是老大,全部都要有
- RIGHT JOIN:右表是老大,全部都要有
- INNER JOIN:只认共同的朋友
- FULL JOIN:大家都是朋友,有没有共同点都行