🔗 掌握多表连接,让你的数据查询能力提升一个档次!本文用最直观的方式讲解MySQL连接查询的核心概念。
一、前言:为什么需要多表连接?
在实际项目中,数据通常分散在多个表中。比如:
学生表:存学生基本信息
成绩表:存学生考试成绩
班级表:存班级信息
如果我们想查询"学生姓名 + 班级名称 + 各科成绩",就需要同时从多个表获取数据。这时候,多表连接就派上用场了!
二、准备工作:创建示例数据库
2.1 创建学生管理系统数据库
bash
sql
-- 创建数据库
CREATE DATABASE IF NOT EXISTS school_db;
USE school_db;
-- 1. 创建学生表
CREATE TABLE students (
student_id INT PRIMARY KEY AUTO_INCREMENT,
student_name VARCHAR(50) NOT NULL,
gender ENUM('男', '女') DEFAULT '男',
age INT,
class_id INT -- 关联班级表的ID
);
-- 2. 创建班级表
CREATE TABLE classes (
class_id INT PRIMARY KEY AUTO_INCREMENT,
class_name VARCHAR(50) NOT NULL,
teacher VARCHAR(50)
);
-- 3. 创建成绩表
CREATE TABLE scores (
score_id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT,
subject VARCHAR(50),
score DECIMAL(5,2),
exam_date DATE
);
-- 4. 创建选课表(用于演示多对多关系)
CREATE TABLE course_selection (
selection_id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT,
course_id INT
);
-- 5. 创建课程表
CREATE TABLE courses (
course_id INT PRIMARY KEY AUTO_INCREMENT,
course_name VARCHAR(50),
credit INT
);
2.2 插入测试数据
bash
sql
-- 插入班级数据
INSERT INTO classes (class_name, teacher) VALUES
('一年级一班', '张老师'),
('一年级二班', '李老师'),
('二年级一班', '王老师'),
('二年级二班', '赵老师');
-- 插入学生数据
INSERT INTO students (student_name, gender, age, class_id) VALUES
('张三', '男', 18, 1),
('李四', '女', 17, 1),
('王五', '男', 19, 2),
('赵六', '女', 18, 2),
('钱七', '男', 20, 3),
('孙八', '女', 19, 3),
('周九', '男', 18, NULL), -- 注意:这个学生没有分配班级
('吴十', '女', 17, NULL); -- 注意:这个学生没有分配班级
-- 插入成绩数据
INSERT INTO scores (student_id, subject, score, exam_date) VALUES
(1, '数学', 85.5, '2023-06-15'),
(1, '语文', 90.0, '2023-06-16'),
(2, '数学', 78.0, '2023-06-15'),
(2, '语文', 88.5, '2023-06-16'),
(3, '数学', 92.0, '2023-06-15'),
(4, '语文', 76.5, '2023-06-16'),
(5, '数学', 89.0, '2023-06-15'),
(6, '语文', 94.5, '2023-06-16'),
(9, '数学', 100.0, '2023-06-15'); -- 注意:学生ID 9不存在于学生表
-- 插入课程数据
INSERT INTO courses (course_name, credit) VALUES
('高等数学', 4),
('大学英语', 3),
('计算机基础', 2),
('数据结构', 4);
-- 插入选课数据
INSERT INTO course_selection (student_id, course_id) VALUES
(1, 1), (1, 2),
(2, 1), (2, 3),
(3, 2), (3, 4),
(4, 1), (4, 2), (4, 3);
2.3 查看各表数据概览
bash
sql
-- 学生表
SELECT * FROM students;
-- 班级表
SELECT * FROM classes;
-- 成绩表
SELECT * FROM scores;
-- 课程表
SELECT * FROM courses;
-- 选课表
SELECT * FROM course_selection;
三、内连接查询(INNER JOIN)
3.1 什么是内连接?
内连接就像"相亲":只返回两个表都匹配的记录。如果某条记录在一个表存在,但在另一个表找不到对应记录,就不会出现在结果中。
3.2 基本语法
bash
sql
-- 写法1:使用INNER JOIN关键字(推荐)
SELECT 列名
FROM 表1
INNER JOIN 表2 ON 连接条件;
-- 写法2:使用WHERE子句(传统写法)
SELECT 列名
FROM 表1, 表2
WHERE 连接条件;
3.3 实例演示:查询学生及其班级信息
bash
sql
-- 查询所有学生及其所在班级
SELECT
s.student_id,
s.student_name,
s.gender,
s.age,
c.class_name,
c.teacher
FROM students s -- s是students表的别名
INNER JOIN classes c ON s.class_id = c.class_id;
查询结果:
text
+------------+--------------+--------+-----+--------------+-----------+
| student_id | student_name | gender | age | class_name | teacher |
+------------+--------------+--------+-----+--------------+-----------+
| 1 | 张三 | 男 | 18 | 一年级一班 | 张老师 |
| 2 | 李四 | 女 | 17 | 一年级一班 | 张老师 |
| 3 | 王五 | 男 | 19 | 一年级二班 | 李老师 |
| 4 | 赵六 | 女 | 18 | 一年级二班 | 李老师 |
| 5 | 钱七 | 男 | 20 | 二年级一班 | 王老师 |
| 6 | 孙八 | 女 | 19 | 二年级一班 | 王老师 |
+------------+--------------+--------+-----+--------------+-----------+
🔍 重要发现:
结果只有6条记录
学生"周九"和"吴十"没有出现在结果中(因为他们没有班级)
这就是内连接的特点:只返回两个表都有的匹配记录
3.4 多表内连接:查询学生成绩详情
bash
sql
-- 查询学生姓名、班级、科目和成绩
SELECT
s.student_name,
c.class_name,
sc.subject,
sc.score,
sc.exam_date
FROM students s
INNER JOIN classes c ON s.class_id = c.class_id
INNER JOIN scores sc ON s.student_id = sc.student_id;
3.5 内连接加条件筛选
bash
sql
-- 查询一年级学生的数学成绩
SELECT
s.student_name,
c.class_name,
sc.score
FROM students s
INNER JOIN classes c ON s.class_id = c.class_id
INNER JOIN scores sc ON s.student_id = sc.student_id
WHERE c.class_name LIKE '一年级%'
AND sc.subject = '数学'
ORDER BY sc.score DESC;
四、外连接查询(OUTER JOIN)
4.1 外连接的概念
外连接就像"相亲+展示":除了返回匹配的记录,还会返回一个表中存在但另一个表没有匹配的记录。
4.2 三种外连接类型
📊 对比表格
连接类型 含义 关键字
左外连接 返回左表所有记录 + 右表匹配记录 LEFT JOIN
右外连接 返回右表所有记录 + 左表匹配记录 RIGHT JOIN
全外连接 返回两个表的所有记录 FULL JOIN
⚠️ 注意:MySQL不支持FULL JOIN,但可以通过UNION实现同样效果
4.3 左外连接(LEFT JOIN)
bash
查询所有学生,包括没有班级的学生
sql
SELECT
s.student_id,
s.student_name,
c.class_name,
c.teacher
FROM students s -- 左表
LEFT JOIN classes c ON s.class_id = c.class_id -- 左连接
ORDER BY s.student_id;
查询结果:
text
+------------+--------------+--------------+-----------+
| student_id | student_name | class_name | teacher |
+------------+--------------+--------------+-----------+
| 1 | 张三 | 一年级一班 | 张老师 |
| 2 | 李四 | 一年级一班 | 张老师 |
| 3 | 王五 | 一年级二班 | 李老师 |
| 4 | 赵六 | 一年级二班 | 李老师 |
| 5 | 钱七 | 二年级一班 | 王老师 |
| 6 | 孙八 | 二年级一班 | 王老师 |
| 7 | 周九 | NULL | NULL | ← 没有班级
| 8 | 吴十 | NULL | NULL | ← 没有班级
+------------+--------------+--------------+-----------+
🔍 重要发现:
所有学生都出现了(包括没有班级的周九和吴十)
对于没有班级的学生,班级信息显示为NULL
左连接保证左表(students)所有记录都会出现
bash
找出没有班级的学生
sql
SELECT
s.student_id,
s.student_name,
s.age
FROM students s
LEFT JOIN classes c ON s.class_id = c.class_id
WHERE c.class_id IS NULL;
4.4 右外连接(RIGHT JOIN)
bash
查询所有班级,包括没有学生的班级
sql
SELECT
c.class_id,
c.class_name,
c.teacher,
s.student_name
FROM students s -- 左表
RIGHT JOIN classes c ON s.class_id = c.class_id -- 右连接
ORDER BY c.class_id;
查询结果:
text
+----------+--------------+-----------+--------------+
| class_id | class_name | teacher | student_name |
+----------+--------------+-----------+--------------+
| 1 | 一年级一班 | 张老师 | 张三 |
| 1 | 一年级一班 | 张老师 | 李四 |
| 2 | 一年级二班 | 李老师 | 王五 |
| 2 | 一年级二班 | 李老师 | 赵六 |
| 3 | 二年级一班 | 王老师 | 钱七 |
| 3 | 二年级一班 | 王老师 | 孙八 |
| 4 | 二年级二班 | 赵老师 | NULL | ← 没有学生
+----------+--------------+-----------+--------------+
🔍 重要发现:
所有班级都出现了(包括没有学生的二年级二班)
对于没有学生的班级,学生信息显示为NULL
右连接保证右表(classes)所有记录都会出现
找出没有学生的班级
bash
sql
SELECT
c.class_id,
c.class_name,
c.teacher
FROM students s
RIGHT JOIN classes c ON s.class_id = c.class_id
WHERE s.student_id IS NULL;
4.5 全外连接(FULL JOIN)的模拟实现
bash
sql
-- MySQL不直接支持FULL JOIN,但可以用UNION模拟
-- 查询所有学生和所有班级的对应关系
SELECT
s.student_id,
s.student_name,
c.class_name
FROM students s
LEFT JOIN classes c ON s.class_id = c.class_id
UNION
SELECT
s.student_id,
s.student_name,
c.class_name
FROM students s
RIGHT JOIN classes c ON s.class_id = c.class_id
WHERE s.student_id IS NULL;
五、内连接 vs 外连接:对比分析
5.1 维恩图理解
bash
text
内连接(INNER JOIN):
students表 classes表
[交集部分] ← 只返回这个
左连接(LEFT JOIN):
[students全部] + 匹配的classes部分
右连接(RIGHT JOIN):
[classes全部] + 匹配的students部分
5.2 实际场景选择指南
实际场景示例
bash
sql
-- 场景1:统计每个班级的学生人数(用内连接)
SELECT
c.class_name,
COUNT(s.student_id) AS student_count
FROM classes c
INNER JOIN students s ON c.class_id = s.class_id
GROUP BY c.class_id;
-- 场景2:统计所有班级学生人数(包括没有学生的班级)
SELECT
c.class_name,
COUNT(s.student_id) AS student_count
FROM classes c
LEFT JOIN students s ON c.class_id = s.class_id
GROUP BY c.class_id;
-- 场景3:查看所有学生的考试情况(包括没考试的学生)
SELECT
s.student_name,
sc.subject,
sc.score
FROM students s
LEFT JOIN scores sc ON s.student_id = sc.student_id;
-- 场景4:查看所有考试成绩(包括不存在的学生成绩)
SELECT
sc.score_id,
sc.subject,
sc.score,
s.student_name
FROM scores sc
LEFT JOIN students s ON sc.student_id = s.student_id
WHERE s.student_id IS NULL; -- 找出无效的成绩记录
六、复杂多表连接实战
6.1 多对多关系查询:学生选课情况
bash
sql
-- 查询学生选课详情(学生 ↔ 课程 多对多)
SELECT
s.student_name,
c.course_name,
c.credit
FROM students s
INNER JOIN course_selection cs ON s.student_id = cs.student_id
INNER JOIN courses c ON cs.course_id = c.course_id
ORDER BY s.student_name, c.course_name;
6.2 自连接:查询同一班级的学生对
bash
sql
-- 创建员工表演示自连接
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(50),
manager_id INT
);
INSERT INTO employees VALUES
(1, '张总', NULL),
(2, '李经理', 1),
(3, '王主管', 2),
(4, '赵员工', 3),
(5, '钱员工', 3);
-- 查询每个员工及其经理
SELECT
e.emp_name AS '员工姓名',
m.emp_name AS '经理姓名'
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.emp_id;
6.3 带聚合函数的多表连接
bash
sql
-- 查询每个班级的平均成绩
SELECT
c.class_name,
AVG(sc.score) AS avg_score,
COUNT(DISTINCT s.student_id) AS student_count,
COUNT(sc.score_id) AS exam_count
FROM classes c
LEFT JOIN students s ON c.class_id = s.class_id
LEFT JOIN scores sc ON s.student_id = sc.student_id
GROUP BY c.class_id
HAVING exam_count > 0; -- 只显示有考试成绩的班级
💡 总结与建议
掌握要点:
内连接用于精确匹配查询
左/右连接用于保证某表数据完整
多表连接时要理清表之间的关系
为连接字段创建索引提升性能
学习建议:
先理解业务需求,再选择连接类型
从简单连接开始,逐步增加复杂度
多用EXPLAIN分析查询性能
在实际项目中多练习