第一部分:数据库表间关系
1.1 三种关系概述
⭐ 老师强调:表间关系只有三种------一对一、一对多、多对多。不存在独立的"多对一",它只是一对多的反向视角,不要单列。
| 关系类型 | 举例 | 最佳处理方案 |
|---|---|---|
| 一对一 | 丈夫 ↔ 妻子 | 合并成一张表 |
| 一对多 | 班级 → 学生 | 在"多"的一方加外键 |
| 多对多 | 学生 ↔ 课程 | 创建中间表 |
1.2 一对一关系
⭐ 老师强调 :一对一关系最佳处理方式是合并成一张表。把两边信息放同一行,如丈夫名和妻子名并排,同处一行即表示对应关系。查询效率最高,因为只需查单表。
| 丈夫信息 | 妻子信息 |
|---|---|
| 张三 | 李华 |
| 李四 | 张明 |
| 王五 | 丽丽 |
⭐ 老师强调 :合并成一张表后,夫妻关系被直接绑定,不能填别人。用两张表加外键虽能存关系,但本质也是一行绑定一个,没有更灵活。一张表查询效率远高于多表联查,因为计算机按页存储,相关信息放同一页,分表后底层识别不到关联字段,会放不同页。
不推荐方案(了解即可):
- 在妻子表加外键指向丈夫表
- 建第三张关系表存丈夫id和妻子id(三表联查效率最低)
1.3 一对多关系
⭐ 老师强调 :一对多关系的标准示例是班级和学生------一个班级对应多个学生,但一个学生只属于一个班级。站在学生角度看是多对一,站在班级看是一对多,本质同一关系。
错误方案:合并成一张表
| 学生名 | 班级名 | 班级人数 |
|---|---|---|
| 张三 | 软件工程班 | 50 |
| 李四 | 软件工程班 | 50 |
| 王五 | 软件工程班 | 50 |
⭐ 老师强调 :合并表造成数据冗余 ------班级信息随学生数量重复存储,有100个学生就存100遍。设计原则不允许数据重复,所以此方案不可行。
✅ 正确方案:在"多"的一方加外键
班级表(一)
┌────┬──────────────┐
│ id │ classname │
├────┼──────────────┤
│ 1 │ 软件工程班 │
│ 2 │ 大数据班 │
└────┴──────────────┘
学生表(多)------加外键cid指向班级表id
┌────┬──────┬─────┐
│ id │ name │ cid │
├────┼──────┼─────┤
│ 1 │ 张三 │ 1 │
│ 2 │ 李四 │ 1 │
│ 3 │ 王五 │ 2 │
└────┴──────┴─────┘
⭐ 老师强调:外键是多了个引用值,值相同不算冗余。外键字段加在学生表(多的一方),而非班级表(一的一方)。若在班级表加学生id,单元格只能存一条信息,软件工程班存张三后李四得另起行,班级信息重复存储。
结论:一对多关系 → 在"多"的一方加外键,不要建中间表(三表联查更慢)。
1.4 多对多关系
⭐ 老师强调 :多对多关系的典型示例是学生和课程------一个学生可选多门课,一门课也可被多个学生选。从单方看都是一对多,但双向都可一对多即多对多。
错误方案:合并成一张表
| 学生名 | 课程名 | 学分 |
|---|---|---|
| 张三 | 数据结构 | 3 |
| 张三 | C语言 | 4 |
| 李四 | 数据结构 | 3 |
⭐ 老师强调 :合并表导致双方数据都冗余 ------张三选10门课得存10遍张三信息,数据结构被2人选得存2遍课程信息。双方信息都重复,不可接受。
错误方案:加外键
- 学生表加课程id字段→只能存单个值,不能表示"1,2,3"
- 课程表加学生id字段→同样问题,违反存储规范
✅ 正确方案:创建中间表
学生表(存学生信息) 课程表(存课程信息)
┌────┬──────┐ ┌────┬──────────┬────┐
│ id │ name │ │ id │ cname │credit│
├────┼──────┤ ├────┼──────────┼────┤
│ 1 │ 张三 │ │ 1 │ 数据结构 │ 3 │
│ 2 │ 李四 │ │ 2 │ C语言 │ 4 │
└────┴──────┘ └────┴──────────┴────┘
中间表(选课关系 + 成绩)
┌────┬─────┬─────┬──────┐
│ id │ sid │ cid │ score│
├────┼─────┼─────┼──────┤
│ 1 │ 1 │ 1 │ 85 │
│ 2 │ 1 │ 2 │ 92 │
│ 3 │ 2 │ 1 │ 78 │
└────┴─────┴─────┴──────┘
⭐ 老师强调:中间表像"桥",把学生和课程解耦。中间表的sid存学生id,cid存课程id,成绩也放中间表。双方核心信息只存一遍,无冗余。宁可接受多表联查的性能损耗,也不容忍数据重复。
1.5 三种关系方案总结
| 关系 | 最佳方案 | 原因 |
|---|---|---|
| 一对一 | 合并成一张表 | 单表查询效率最高 |
| 一对多 | 在"多"方加外键 | 避免一方的数据重复 |
| 多对多 | 创建中间表 | 避免双方数据重复 |
第二部分:多表查询与联表查询
2.1 数据准备
-- 学生表
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
cid INT -- 指向班级id
);
-- 班级表
CREATE TABLE class (
id INT PRIMARY KEY AUTO_INCREMENT,
classname VARCHAR(50)
);
-- 示例数据
INSERT INTO student VALUES (1, '张三', 1), (2, '李四', 1), (3, '王五', 2), (4, '赵六', NULL);
INSERT INTO class VALUES (1, '软件工程班'), (2, '大数据班'), (3, '人工智能班');
2.2 多表查询(隐式内连接)
⭐ 老师强调 :多表查询用逗号分隔表名,在WHERE中写连接条件。不加条件会产生笛卡尔积------A表4条×B表10条=40条,每条学生匹配每个班级。
-- ❌ 错误:不加条件 → 笛卡尔积(14行)
SELECT * FROM student, class; -- 7条×2条=14条
-- ✅ 正确:加连接条件
SELECT student.*, class.classname
FROM student, class
WHERE student.cid = class.id;
⭐ 老师强调 :多表查询本质是内连接,只保留两表都能匹配的记录。空值不参与匹配,所以未分班的赵六、无学生的班级在结果中"消失"------容易误以为数据完整,实则丢了记录。
2.3 联表查询(JOIN)
三种JOIN对比:
-- 内连接:只保留匹配记录(等价于多表查询)
SELECT * FROM student JOIN class ON student.cid = class.id;
-- 左外连接:保留左表全部 + 右表匹配部分
SELECT * FROM student LEFT JOIN class ON student.cid = class.id;
-- 右外连接:保留右表全部 + 左表匹配部分
SELECT * FROM student RIGHT JOIN class ON student.cid = class.id;
| 连接类型 | 保留内容 | 使用场景 |
|---|---|---|
INNER JOIN |
两表都匹配的记录 | 只想看有对应关系的数据 |
LEFT JOIN |
左表全部 + 右表匹配 | 以左表为主,保留左表所有数据 |
RIGHT JOIN |
右表全部 + 左表匹配 | 以右表为主,保留右表所有数据 |
示例效果:
student LEFT JOIN class:
┌────┬──────┬─────┬─────────────┐
│ id │ name │ cid │ classname │
├────┼──────┼─────┼─────────────┤
│ 1 │ 张三 │ 1 │ 软件工程班 │
│ 2 │ 李四 │ 1 │ 软件工程班 │
│ 3 │ 王五 │ 2 │ 大数据班 │
│ 4 │ 赵六 │NULL │ NULL │ ← 左表无匹配也保留
└────┴──────┴─────┴─────────────┘
student RIGHT JOIN class:
┌────┬──────┬─────┬─────────────┐
│ id │ name │ cid │ classname │
├────┼──────┼─────┼─────────────┤
│ 1 │ 张三 │ 1 │ 软件工程班 │
│ 2 │ 李四 │ 1 │ 软件工程班 │
│ 3 │ 王五 │ 2 │ 大数据班 │
│NULL│ NULL │NULL │ 人工智能班 │ ← 右表无匹配也保留
└────┴──────┴─────┴─────────────┘
2.4 表重命名(别名)
⭐ 老师强调:重命名后用新名,原表名失效。字段名冲突时必须加表名/别名前缀区分。
-- 表重命名
SELECT * FROM student s JOIN class c ON s.cid = c.id;
-- 字段重命名(AS可省略)
SELECT s.name AS 学生姓名, c.classname AS 班级名称
FROM student s JOIN class c ON s.cid = c.id;
2.5 多表联查示例(三表)
-- 查询学生选了哪些课、成绩多少
-- 学生表 → 选课表 → 课程表
SELECT s.name, c.cname, sc.score
FROM student s
JOIN sc ON s.id = sc.sid
JOIN course c ON sc.cid = c.id;
⭐ 老师强调:多表JOIN时,每个JOIN都需要ON条件。学生表和课程表无直接关联,需通过选课表中转:先JOIN选课表,再JOIN课程表。
第三部分:嵌套查询
3.1 什么是嵌套查询
⭐ 老师强调 :嵌套查询是一个SQL查询的结果作为另一个SQL的条件或临时表使用。即查询嵌套进查询里。
子查询的两种用法:
| 用法 | 位置 | 说明 |
|---|---|---|
| 作为条件 | WHERE子句中 | 子查询结果作为过滤条件 |
| 作为临时表 | FROM子句中 | 子查询结果当成表参与JOIN |
3.2 子查询作为条件
-- 需求:查出至少有一科考过85分的学生信息
-- 步骤1:查成绩表,找出考过85分的学生id
SELECT sid FROM sc WHERE score > 85; -- 得到 1, 2
-- 步骤2:用这些id去学生表查信息
SELECT * FROM student WHERE id IN (1, 2);
-- 嵌套写法(一条SQL完成)
SELECT * FROM student
WHERE id IN (SELECT sid FROM sc WHERE score > 85);
3.3 子查询作为临时表
⭐ 老师强调 :子查询结果可以当成一张临时表,在FROM子句中重命名后参与JOIN。
-- 把查询结果当临时表使用
SELECT s1.*, temp.cname
FROM course s1
JOIN (SELECT cid, cname FROM course WHERE credit > 3) temp
ON s1.id = temp.cid;
附录一:完整SQL示例
-- 1. 多表查询(隐式内连接)
SELECT student.*, class.classname
FROM student, class
WHERE student.cid = class.id;
-- 2. 内连接(等价)
SELECT * FROM student JOIN class ON student.cid = class.id;
-- 3. 左外连接
SELECT * FROM student LEFT JOIN class ON student.cid = class.id;
-- 4. 右外连接
SELECT * FROM student RIGHT JOIN class ON student.cid = class.id;
-- 5. 表重命名
SELECT * FROM student s JOIN class c ON s.cid = c.id;
-- 6. 三表联查
SELECT s.name, c.cname, sc.score
FROM student s
JOIN sc ON s.id = sc.sid
JOIN course c ON sc.cid = c.id;
-- 7. 嵌套查询(作为条件)
SELECT * FROM student
WHERE id IN (SELECT sid FROM sc WHERE score > 85);
-- 8. 嵌套查询(作为临时表)
SELECT s1.*, temp.cname
FROM course s1
JOIN (SELECT cid, cname FROM course WHERE credit > 3) temp
ON s1.id = temp.cid;
附录二:核心易错点速查表
| 类别 | 易错点 | 正确理解 |
|---|---|---|
| 一对一 | 分两张表加外键 | 最佳方案是合并成一张表 |
| 一对多 | 在一的一方加外键 | 在多的一方加外键 |
| 多对多 | 在某一张表加外键 | 必须创建中间表 |
| 外键 vs 冗余 | 以为加外键就是冗余 | 加外键只是引用值,不算冗余 |
| 多表查询不加条件 | 以为直接出正确结果 | 产生笛卡尔积 |
| INNER JOIN | 以为能保留所有数据 | 只保留匹配记录,无匹配的会消失 |
| LEFT/RIGHT JOIN | 左右表搞反 | 看表书写顺序,左表全部保留 |
| 表重命名 | 以为旧名还能用 | 重命名后必须用新名 |
| JOIN条件 | 多个JOIN只写一个ON | 每个JOIN都要有ON条件 |
| 嵌套查询 | 混淆"当条件"和"当表" | 条件在WHERE,临时表在FROM |
⭐ 老师总结 :表间关系三种------一对一合并表,一对多"多"方加外键,多对多建中间表。多表查询用逗号+WHERE本质是内连接,只保留匹配记录;联表查询用JOIN,INNER JOIN等价多表查询,LEFT/RIGHT JOIN可保留一侧全部数据。嵌套查询是子查询结果作为条件(WHERE)或临时表(FROM)使用。外键在开发中一般不在数据库设约束,靠业务代码控制取值合法性。 多表查询关键是连接条件,写对才能出正确结果。