函数依赖
函数依赖可以理解为
通过一个属性可以唯一确定另一个属性的值。
举例:
-
ISBN -> 书名,作者,出版社
意思是:如果你知道一本书的 ISBN,就能唯一确定这本书的 书名、作者 和 出版社。因为 ISBN 是每本书独一无二的。 -
书名 -> 作者
假设图书馆只存一本书的一个版本,那么知道一本书的 书名,你也就知道了这本书的 作者。例如:- "西游记" -> "吴承恩"
- "红楼梦" -> "曹雪芹"
-
书名 -> 出版社
如果每本书只有一个版本(只有一个出版社出版),那么知道 书名 就可以确定 出版社。但如果一本书有多个版本(不同出版社出版),这个依赖就不成立了。 - 知道身份证号(类似主键),就知道一个人的所有信息。
- 知道车牌号,就能找到车辆的注册信息。
平凡函数依赖
定义:如果一个函数依赖的右边属性是左边属性的子集,称为平凡函数依赖。
换句话说,你知道的本来就包括右边的信息,这没什么意义。
在图书馆的图书表中(属性:ISBN, 书名, 作者, 出版社):
-
(ISBN, 书名) → 书名
- 这是平凡函数依赖,因为右边的 书名 已经包含在左边的属性 (ISBN, 书名) 中。
- 换句话说:既然你已经知道 ISBN 和书名了,书名当然是确定的,这没什么新信息。
-
(书名, 作者) → 作者
- 这也是平凡函数依赖,因为右边的 作者 是左边的一个属性。
非平凡函数依赖
定义:如果一个函数依赖的右边属性不是左边属性的子集,称为非平凡函数依赖。
换句话说,右边的属性依赖于左边的信息,是新得出的结论。
-
ISBN → 书名
- 这是非平凡函数依赖,因为 书名 不属于左边的属性(ISBN)。
- 意义在于:通过 ISBN,你可以唯一确定书名。
-
书名 → 作者
- 如果每本书只有一个作者,这就是非平凡函数依赖。
- 意义在于:通过书名,可以确定作者。
完全函数依赖
定义:在一个复合键(由多个属性组成的键)中,如果右边的属性完全依赖于整个键,而不是键的一部分,这种依赖关系称为完全函数依赖。
假设图书馆的表有以下属性:
- (ISBN, 分馆) → 馆藏数量
- ISBN:图书的国际标准书号。
- 分馆:图书馆的分馆名称。
- 馆藏数量:某分馆的某本书的库存量。
这里 (ISBN, 分馆) 是复合键。
-
完全函数依赖:
(ISBN, 分馆) → 馆藏数量
要确定某本书的馆藏数量,你需要同时知道 ISBN 和 分馆,缺一不可。例如:ISBN = "12345",分馆 = "A馆",对应馆藏数量是 10;如果只知道 ISBN,你无法确定分馆的馆藏数量。
这就是完全函数依赖,因为馆藏数量依赖于整个复合键 (ISBN, 分馆),而不是键的一部分。
部分函数依赖
定义:在一个复合键中,如果右边的属性依赖于复合键的一部分(而不是整个键),这种依赖关系称为部分函数依赖。
假设同样的图书馆表中:
-
部分函数依赖:
ISBN → 书名
知道 ISBN 就能确定书名,而与分馆无关。
例如:ISBN = "12345",书名是《红楼梦》。书名只依赖 ISBN,而不依赖复合键的另一部分 分馆。
范式
假设一个学生选课系统,有以下初始表:
学号 | 学生姓名 | 学院 | 课程名 | 任课老师 | 教室 |
---|---|---|---|---|---|
1001 | 张三 | 计算机学院 | 数据库 | 王老师 | A101 |
1002 | 李四 | 数学学院 | 高数 | 李老师 | B202 |
1001 | 张三 | 计算机学院 | 高数 | 李老师 | B202 |
第一范式(1NF)——消除重复数据,所有列的值必须是原子值
- 要求:表中的每一列只能存储单一值,不能是列表或集合。也就是说,数据必须是“原子化”的。
不符合1NF的情况:
学号 | 学生姓名 | 学院 | 课程名 | 任课老师 | 教室 |
---|---|---|---|---|---|
1001 | 张三 | 计算机学院 | 数据库, 高数 | 王老师, 李老师 | A101, B202 |
这里的“课程名”、“任课老师”、“教室”列中存储了多个值,不是原子化的。
1NF 转换:
将每个课程分成单独的记录:
学号 | 学生姓名 | 学院 | 课程名 | 任课老师 | 教室 |
---|---|---|---|---|---|
1001 | 张三 | 计算机学院 | 数据库 | 王老师 | A101 |
1002 | 李四 | 数学学院 | 高数 | 李老师 | B202 |
1001 | 张三 | 计算机学院 | 高数 | 李老师 | B202 |
1NF 解决了数据的非原子化问题。
第二范式(2NF)——消除部分依赖
- 要求:表必须满足 1NF,且非主属性完全依赖于主键,不能存在部分函数依赖。
在上面的例子中主键是 (学号, 课程名)(因为“学号+课程名”唯一标识一条记录),我们发现:
- 学生姓名和学院只依赖于“学号”,而不依赖于复合主键的“课程名”部分(部分依赖)。
- 这会导致冗余:如果同一个学生选了多门课,学生的姓名和学院信息会重复存储。
2NF 转换:
拆分表,消除部分依赖:
-
学生信息表(只存储与“学号”相关的信息):
学号 学生姓名 学院 1001 张三 计算机学院 1002 李四 数学学院 -
选课信息表(只存储与“学号+课程名”相关的信息):
学号 课程名 任课老师 教室 1001 数据库 王老师 A101 1001 高数 李老师 B202 1002 高数 李老师 B202
2NF 解决了部分函数依赖,减少了数据冗余。
第三范式(3NF)——消除传递依赖
- 要求:表必须满足 2NF,且非主属性不能传递依赖于主键。
在选课信息表中:
- “教室”依赖于“课程名”(课程名决定教室)。
- “课程名”又依赖于主键“(学号, 课程名)”。
- 因此,“教室”是通过“课程名”传递依赖于主键的,这会导致冗余:同一个课程的教室信息可能被重复存储。
3NF 转换:
进一步拆分表,消除传递依赖:
-
学生信息表:
学号 学生姓名 学院 1001 张三 计算机学院 1002 李四 数学学院 -
选课信息表:
学号 课程名 1001 数据库 1001 高数 1002 高数 -
课程信息表:
课程名 任课老师 教室 数据库 王老师 A101 高数 李老师 B202
3NF 解决了传递依赖问题,进一步减少了数据冗余。
BCNF(Boyce-Codd Normal Form)——更严格的第三范式
- 要求:在每个函数依赖中,左边的属性必须是候选键。
特殊情况:
3NF 中如果存在非主属性决定主属性的情况,就不满足 BCNF。
例如:如果选课表变成这样:
学号 | 课程名 | 任课老师 |
---|---|---|
1001 | 数据库 | 王老师 |
1001 | 高数 | 李老师 |
1002 | 高数 | 李老师 |
这里存在:
- 任课老师 → 课程名(任课老师决定课程名)。
- 但是“任课老师”不是候选键,违背了 BCNF。
BCNF 转换:
将表拆分为:
-
学生选课表:
学号 课程名 1001 数据库 1001 高数 1002 高数 -
任课老师表:
课程名 任课老师 数据库 王老师 高数 李老师
总结
范式 | 核心问题 | 解决方法 |
---|---|---|
1NF | 数据非原子化(多值或集合) | 拆分成单一值的记录 |
2NF | 存在部分函数依赖 | 拆分表,消除部分依赖 |
3NF | 存在传递依赖 | 拆分表,消除传递依赖 |
BCNF | 非候选键的属性决定其他属性 | 更严格地规范主键和依赖关系 |