第一范式(1NF):原子性
第一范式(1NF)是关系数据库设计中最基本的规范化形式,其核心要求是原子性(Atomicity)。
原子性的含义
原子性要求数据库表中的每个列(字段)都包含不可再分的最小数据单元,即:
- 每个字段的值都是单一的、不可分割的
- 不允许在一个字段中存储多个值或复合值
- 不允许存在重复的列或列组
违反1NF的示例
以下表结构违反了1NF:
|--------|--------|-------------------------|
| 学号 | 姓名 | 联系电话 |
| 1001 | 张三 | 13800138000,13800138001 |
| 1002 | 李四 | 13900139000 |
问题在于"联系电话"列包含了多个值(用逗号分隔)。
符合1NF的修正方案
方案一:拆分多值字段
学生表(1NF):
|------|----|-------------|
| 学号 | 姓名 | 联系电话 |
| 1001 | 张三 | 13800138000 |
| 1001 | 张三 | 13800138001 |
| 1002 | 李四 | 13900139000 |
方案二:创建关联表
学生基本信息表:
|------|----|
| 学号 | 姓名 |
| 1001 | 张三 |
| 1002 | 李四 |
学生电话表:
|------|-------------|
| 学号 | 联系电话 |
| 1001 | 13800138000 |
| 1001 | 13800138001 |
| 1002 | 13900139000 |
1NF的其他要求
除了原子性外,1NF还要求:
- 表中的每一行必须是唯一的(通常通过主键保证)
- 列的顺序不重要
- 行的顺序不重要
满足1NF是数据库规范化的第一步,为后续的2NF、3NF等更高级别的规范化奠定了基础。
1NF的实际意义
- 禁止"大杂烩字段"
------ 比如不能把"电话1,电话2,电话3"挤在一个格子里,必须拆成多行或多列 - 消灭表格里的"套娃数据"
------ 像"地址:北京市{海淀区{中关村大街5号}}"这种嵌套结构要拍平 - 让数据库能正常做计算
------ 如果一列里既有数字又有文字,连SUM求和都没法用 - 避免数据黏成一团
------ 比如"张三,李四,王五"挤在一个单元格里,想单独查李四的记录会疯掉
(本质上就是逼着你**把数据拆到最小颗粒度,**就像乐高积木必须用单个颗粒而不是粘死的整块)
第二范式(2NF):消除部分依赖
第二范式(2NF)建立在第一范式(1NF)的基础上,主要解决部分函数依赖的问题。
2NF的核心要求
- 首先满足1NF的所有条件
- 所有非主键字段必须完全函数依赖于整个主键(不能只依赖于主键的一部分)
"每个非主键信息必须由整个主键决定,不能只由主键的一部分决定。"
比如学生选课表中,成绩由"学号+课程号"共同决定(符合2NF),但学生姓名只由学号决定(违反2NF)。
关键概念解释
- 函数依赖:如果知道X的值就能确定Y的值,则称Y函数依赖于X(X→Y)
- 完全函数依赖:非主键字段依赖于整个主键,而不是主键的某一部分
- 部分函数依赖:非主键字段只依赖于主键的一部分(违反2NF)
违反2NF的经典例子
学生选课表(违反2NF)
|--------|---------|----------|--------|--------|----------|----------|
| 学号 | 课程号 | 课程名称 | 学分 | 成绩 | 学生姓名 | 学生系别 |
| 001 | C01 | 数据库 | 4 | 90 | 张三 | 计算机 |
| 001 | C02 | 算法 | 3 | 85 | 张三 | 计算机 |
| 002 | C01 | 数据库 | 4 | 88 | 李四 | 数学 |
问题分析:
- 主键是复合主键(学号+课程号)
- "成绩"字段完全依赖于整个主键(学号+课程号)→ 符合2NF
- 但"课程名称"和"学分"只依赖于"课程号"(主键的一部分)→ 部分依赖
- "学生姓名"和"学生系别"只依赖于"学号"(主键的一部分)→ 部分依赖
符合2NF的解决方案
将表拆分为三个表,消除部分依赖:
1. 学生表(主键:学号)
|--------|----------|----------|
| 学号 | 学生姓名 | 学生系别 |
| 001 | 张三 | 计算机 |
| 002 | 李四 | 数学 |
2. 课程表(主键:课程号)
|---------|----------|--------|
| 课程号 | 课程名称 | 学分 |
| C01 | 数据库 | 4 |
| C02 | 算法 | 3 |
3. 选课成绩表(主键:学号+课程号)
|-----|-----|----|
| 学号 | 课程号 | 成绩 |
| 001 | C01 | 90 |
| 001 | C02 | 85 |
| 002 | C01 | 88 |
为什么这样设计符合2NF?
- 学生表中,所有非主键字段(姓名、系别)完全依赖于主键(学号)
- 课程表中,所有非主键字段(名称、学分)完全依赖于主键(课程号)
- 选课成绩表中,成绩字段完全依赖于整个主键(学号+课程号)
这样就消除了所有部分函数依赖,达到了2NF的要求。
2NF的实际意义
- 减少数据冗余(如课程信息不再重复存储)
- 避免更新异常(如修改某课程名称只需修改一处)
- 为后续的3NF规范化奠定基础
2NF的实际意义
"消灭重复数据,避免改一处要动全身的麻烦"
具体来说:
- 拒绝"捆绑销售"
------ 比如学生选课表里,每次选课都重复存储学生姓名、系别(浪费空间) - 避免"牵一发动全身"
------ 如果张三转系,不用在几百条选课记录里逐个修改他的系别,只需改学生表里的一条数据 - 让数据各回各家
------ 学生信息归学生表,课程信息归课程表,成绩单独存(就像图书馆把书、借阅记录、读者信息分开管理)
本质:把"一坨数据"拆成多个专业小表,像乐高说明书要求你把零件先分类,而不是混在一起乱拼。
第三范式(3NF):消灭"间接依赖"
一句话解释:"所有非主键字段必须直接依赖主键,不能通过其他非主键字段间接依赖。"
(简单说:表中的数据只能由主键决定,不能由其他非主键字段决定。)
3NF的核心要求
- 首先满足2NF(所有非主键字段完全依赖主键)。
- 不能存在"传递依赖"(即A→B→C,但C不直接依赖A)。
违反3NF的例子
学生住宿表(违反3NF)
|--------|--------|---------|----------|
| 学号 | 姓名 | 宿舍楼 | 宿舍费用 |
| 1001 | 张三 | A栋 | 1200 |
| 1002 | 李四 | B栋 | 1500 |
| 1003 | 王五 | A栋 | 1200 |
问题分析:
- 主键是学号(唯一标识学生)。
- 宿舍费用并不直接由学号决定,而是由宿舍楼决定(学号→宿舍楼→宿舍费用)。
- 这导致:
-
- 数据冗余(A栋的费用重复存储)。
- 更新麻烦(如果A栋费用涨到1300,要改多条记录)。
符合3NF的解决方案
拆成两个表,消除传递依赖:
- 学生表(主键:学号)
|--------|--------|---------|
| 学号 | 姓名 | 宿舍楼 |
| 1001 | 张三 | A栋 |
| 1002 | 李四 | B栋 |
| 1003 | 王五 | A栋 |
- 宿舍费用表(主键:宿舍楼)
|---------|----------|
| 宿舍楼 | 宿舍费用 |
| A栋 | 1200 |
| B栋 | 1500 |
为什么这样更好?
- 减少冗余:宿舍费用只存一次。
- 更新方便:改A栋费用只需改一条记录。
- 查询灵活:可以单独查宿舍费用,不影响学生信息。
3NF的实际意义
- 避免"数据连环改"(比如宿舍费用调整时,不用逐个修改学生记录)。
- 减少存储浪费(相同数据不重复存)。
- 让表更"纯粹"(每张表只描述一种东西,比如学生表只管学生,宿舍表只管宿舍)。
总结:
- 1NF:数据拆到最小(不能一个格子存多个值)。
- 2NF :数据不能"半依赖"主键(必须完全依赖)。
- 3NF:数据不能"拐弯依赖"主键(必须直接依赖)。
3NF之后还有BCNF、4NF等,但3NF已经能解决大多数实际问题!
问三个问题:
- 所有字段都是原子的吗?(1NF)
- 所有非主键字段都完全依赖整个主键吗?(2NF)
- 非主键字段之间没有依赖关系吗?(3NF)