📄 一句话概括
第二范式(2NF):满足 1NF 之后,非主键列必须认整个主键——复合主键里只认一半的列,请搬去它真正归属的表。这消灭的是「部分依赖」。
📝 本节和前面几节的关系
三台阶第二阶。它只在复合主键场景出现(单列主键的表天生满足 2NF)。核心动作:把「只认半个主键」的列拆回自己家——拆完的形状正是 5-6 的中间表。
一、问题表:半个主键养活一列
成绩(学号+课程号 PK, 成绩, 课程名, 学分)
- 成绩:要学号+课程号两个才认出(哪个人哪门课)→ 认整个主键 ✅
- 课程名/学分:只看课程号就定了,学号根本用不上 → 只认半个主键 ❌ 部分依赖
二、部分依赖的三宗罪
- 冗余:500 人选课,「数据库原理」的名字学分抄 500 遍
- 更新异常:课程改名要改 500 行,漏一行就两个版本
- 插不进:新课还没有人选,课程名往哪放?(主键缺一半,整行不能存在)
三、整改:各回各家
成绩(学号 FK, 课程号 FK, 成绩) ← 中间表:整键依赖,干干净净
课程(课程号 PK, 课程名, 学分) ← 被拆出来的「其他种类」
2NF 的动作=把「只认半个主键」的列,搬进以那半个主键为主键的表——正是 5-6 多对多中间表的标准形状。
四、动手 Demo:冗余当场现形
-- 违规表:课程信息跟着每条成绩重复
CREATE TABLE bad_score (
student_id INT,
course_id INT,
score DECIMAL(5,2),
course_name VARCHAR(20), -- 部分依赖!
PRIMARY KEY (student_id, course_id)
);
INSERT INTO bad_score VALUES (1,10,85,'数学'),(2,10,90,'数学'),(3,10,78,'数学');
-- 「数学」抄了 3 遍;课程改名要改 3 行
-- 2NF 整改后
CREATE TABLE courses (course_id INT PRIMARY KEY, course_name VARCHAR(20));
CREATE TABLE scores (
student_id INT, course_id INT, score DECIMAL(5,2),
PRIMARY KEY (student_id, course_id)
);
课程改名:违规表改 3 行(500 行呢?),规范表改 1 行。
五、30 秒实验
单列主键的表会有部分依赖吗?(不会——主键只有一列,要么依赖它要么不依赖;「不依赖」的列留给 3NF 处理。)所以 2NF 只在复合主键表上有戏份。
六、常见误区
- 「2NF 是一切表的必查项」:只对复合主键表生效;自增 id 单主键的表默认通过
- 「拆出去的列丢了」:没丢,搬进了课程表;成绩表留着外键随时 JOIN 回来
自测三问
1. 2NF 消灭什么?(部分依赖——非主键列只认复合主键的一半) 2. 为什么单列主键表天然满足 2NF?(主键只有一列,不存在认一半) 3. 部分依赖的三宗罪?(冗余/更新异常/插不进新课)- ☐ 下一节(5-13 第三范式-拆分处于从属关系中的项目)
相关笔记
- 第一范式-避免重复的项目 —— 上一节:第一台阶
- 第三范式-拆分处于从属关系中的项目 —— 下一节:最后一阶
- 关系、一对多、多对多、一对一-思考数据之间的关系 —— 拆完的形状就是中间表
- 🗄️ 数据库基础 · 索引 —— 全课路线图