函数依赖
函数依赖(Functional Dependency)描述表中列之间的"决定关系"——如果学号确定,姓名就唯一确定,就说"学号 → 姓名"
🔍 “学号确定了,我是谁就确定了”
想想你的学生证:上面印着学号、姓名、学院、班级……
如果有人给你一个学号,你能根据学号查到对应学生的姓名吗?能。 因为学号和姓名是对应的——一个学号只对应一个学生。
反过来——如果有人给你一个姓名,你能根据姓名查到对应的学号吗?不一定。 因为可能有同名同姓的人,一个姓名可能对应多个学号。
这种”一个值确定另一个值”的关系,在数据库理论中被称为 函数依赖(Functional Dependency)。
🧮 名字里为什么有”函数”?
回想你学过的数学函数:f(x) = y——给定输入 x,输出 y 被唯一确定。
函数依赖也是这个意思:给定学号 x,姓名 y 就被唯一确定。 写成:
学号 → 姓名(学号决定姓名)
记法:X → Y,读作”X 函数决定 Y”或”Y 函数依赖于 X”。
🎯 函数依赖的基本表达
函数依赖用 X → Y 表示:对于表中的任意两行,如果它们的 X 值相同,那么它们的 Y 值也必须相同。
用实际例子理解:
学生表:
学号 → 姓名 (知道学号,就知道唯一姓名)
学号 → 年龄 (知道学号,就知道唯一年龄)
学号 → 班级 (知道学号,就知道唯一班级)
学号 → 学院 (知道学号,就知道唯一学院)
系名 → 院长 (知道系名,就知道唯一院长)
系名 → 学号 ❌(一个系有很多学生,系名不能唯一确定学号)
🏫 类比:指纹识别
每个人的指纹能唯一确定一个人。知道指纹 → 就知道姓名、年龄、住址……(指纹 → 姓名、指纹 → 年龄……)
但反过来——知道姓名不一定能确定指纹(可能有重名)。
函数依赖就是描述这种”一件事决定了另一件事”的关系。
🔑 候选键(Candidate Key)
函数依赖和”键”有直接关系。候选键(Candidate Key) 就是能唯一确定一行所有属性的最小属性集。
假设学生表有:学号, 姓名, 年龄, 班级, 身份证号
候选键有两个:
1. {学号} → 能确定所有其他列
2. {身份证号} → 也能确定所有其他列
主键 = 从候选键中选一个,通常是 {学号}
关键概念:候选键的”最小性”——如果你去掉候选键中的任何一个属性,它就不再能唯一确定一行了。
💡 比如 {学号, 姓名} 也能确定所有列,但它不是最小的——因为去掉”姓名”后,{学号} 已经能确定所有列了。所以 {学号, 姓名} 不是候选键。
🔄 Armstrong 公理——函数依赖的”推导规则”
函数依赖之间不是孤立的——给出一组函数依赖,你可以推导出其他的。Armstrong 公理就是推导的规则集。
三条基本规则
① 自反律(Reflexivity)
如果 Y 是 X 的子集,那么 X → Y。
例子:{学号, 姓名} → 学号
解释:{学号, 姓名} 这个组合当然能确定"学号"本身——这看起来是废话,但确保了一致性。
② 增广律(Augmentation)
如果 X → Y,那么 XZ → YZ(在两边同时加上同样的属性,依赖仍然成立)。
已知:学号 → 姓名
那么:(学号, 班级) → (姓名, 班级)
解释:如果学号确定姓名,那么学号+班级自然也确定姓名+班级。
③ 传递律(Transitivity)
如果 X → Y 且 Y → Z,那么 X → Z。
已知:学号 → 系名,系名 → 院长
那么:学号 → 院长
解释:学号决定系名,系名决定院长,所以学号间接决定了院长。
这三条规则看起来简单,但它们构成的系统是完备的——任何可以用函数依赖表达的逻辑推论,都能通过这三条规则推导出来。
一个推导示例
已知:学号 → 系名,系名 → 院长,系名 → 办公楼
由传递律:
- 学号 → 院长(学号 → 系名,系名 → 院长)
- 学号 → 办公楼(学号 → 系名,系名 → 办公楼)
所以学号函数决定了院长的信息——即使学号和院长之间没有直接关系。
⚠️ 为什么要关心函数依赖?
函数依赖是数据库设计质量的重要衡量标准。
不好的表设计往往包含”不合理的函数依赖”:
-- 坏设计:所有信息塞在一张表
CREATE TABLE bad_schema (
student_id INT PRIMARY KEY,
student_name VARCHAR(50),
dept_name VARCHAR(50),
dean_name VARCHAR(50), -- 院长姓名
course_name VARCHAR(100),
grade INT
);
在这个表中存在三个函数依赖:
- student_id → student_name, dept_name(学号决定姓名和系名)
- dept_name → dean_name(系名决定院长)
- (student_id, course_name) → grade(学号+课程名决定成绩)
问题出在哪里?
- 系名 → 院长这个依赖导致:如果某个系有 1000 个学生选了课,“院长姓名”就被重复存储了 1000 次(冗余)
- 如果院长换了,要更新所有包含该院长的行(更新异常)
- 如果一个学生还一门课都没选,他的信息甚至存不进去(插入异常)
- 如果删除所有选了某门课的学生,课程信息也可能被删掉(删除异常)
如何解决这些问题?——这就是下一节 范式 的内容。
📝 小结
| 概念 | 一句话 |
|---|---|
| 函数依赖 X → Y | X 的值决定 Y 的值——X 和 Y 是列名 |
| 候选键 | 能唯一确定所有列的最小属性集 |
| 自反律 | 子集函数依赖于父集 |
| 增广律 | 两边同时加属性,依赖不变 |
| 传递律 | X → Y, Y → Z ⇒ X → Z |
| 函数依赖的用途 | 分析表设计质量,发现冗余和异常 |
🎯 思考题:如果一个表中有”身份证号 → 学号”这个函数依赖,但身份证号不是主键,这算不算好设计?为什么?
为什么先学这个? 函数依赖是衡量表设计”好坏”的数学工具。下一步用这个工具来学习范式(1NF ~ BCNF)——如何通过拆分表来消除不合理的依赖。