关系数据库理论¶
规范化解决的问题¶
把很多信息塞进一张大表,容易出现(以教学管理为例):
-
数据冗余:系名、系主任等随选课重复存储。
-
插入异常:主码含学号时,没学生的新课可能无法插入。
-
删除异常:删光某系学生时,系信息也跟着没了。
-
更新异常:换系主任要改很多行,易不一致。
思路:通过 关系模式的分解,使每个关系描述尽量单一的概念或联系,并满足一定范式要求。
冗余一眼能看出的表
大表一行里同时有:学号、姓名、系号、系名、系主任、课号、成绩。张三选两门课,系名和系主任会重复两行;这就是后面要用函数依赖和范式去「拆开」的对象。
函数依赖¶
语义¶
- 若属性组 \(X\) 的值能唯一决定 \(Y\) 的值,则称 \(Y\) 函数依赖于 \(X\),记作 \(X \to Y\)。
Example
-
业务规定「学号全校唯一」:则 学号 → 姓名(给定学号,姓名至多一个取值)。
-
若允许重名:则一般不能写 姓名 → 学号(同一姓名可能对应多名学生)。
Tip
依赖是对模式上一切合法实例都要成立的约束,不是「看当前这张表凑出来的」;是否成立由业务规则决定(例如是否允许重名会决定「姓名→年龄」是否成立)。
常用术语¶
Tip
非平凡依赖一般指 \(Y\) 不是 \(X\) 的子集。
-
决定因子:\(X \to Y\) 中的 \(X\)。
-
完全 / 部分依赖:若 \(X \to Y\),且 \(X\) 的任一真子集都不能决定 \(Y\)(真子集 \(X' \not\to Y\)),则 \(Y\) 完全依赖于 \(X\);若存在 \(X\) 的某一真子集 \(X'\) 使 \(X' \to Y\),则 \(Y\) 部分依赖于 \(X\)。(分析 2NF 时常看非主属性对候选码是完全还是部分依赖。)
完全函数依赖 vs 部分函数依赖
设关系 SC(学号, 课号, 成绩, 姓名),业务上有 (学号, 课号) → 成绩,且 学号 → 姓名:
-
若候选码是 (学号, 课号),则 成绩 完全依赖于 (学号, 课号)(单靠学号或单靠课号都决定不了成绩);
-
而 姓名 只由 学号 就能决定,即 姓名 部分依赖于 (学号, 课号)——这就是典型的 2NF 问题来源。
-
-
传递依赖:\(X \to Y\),\(Y \to Z\),且 \(Y\) 不决定 \(X\)、\(Z\) 不⊆\(Y\) 等条件满足时,\(Z\) 传递依赖于 \(X\)。
传递依赖(学生—系)
学生(学号, 系号, 系名),若 学号 → 系号 且 系号 → 系名,且系号不能由学号单独反向唯一决定等条件满足,则 系名 传递依赖于 学号。换系名时如果只改部分行,易出现不一致——这正是 3NF 要规避的一类情况。
-
候选码:属性(组)\(K\) 若满足 \(K \to U\)(决定全部属性),且 \(K\) 的任一真子集都不能决定 \(U\),则 \(K\) 为候选码;任选其一可作主码。出现在任一候选码中的属性为主属性,否则为非主属性。
若 \(X\) 不是本关系的码,但是别的关系的码,则 \(X\) 是本关系的外码,用来表示表间联系。候选码、主属性、外码
-
选课(学号, 课号, 成绩):若只有 (学号, 课号) → 成绩 这类依赖,则候选码常是 (学号, 课号);学号、课号 出现在候选码中,是主属性;成绩 不在任何候选码里,是非主属性。
-
选课 中的 学号 引用 学生(学号, …) 的主码,则选课表里的 学号 是外码,表示「哪位学生」选了课。
求候选码的快捷想法
把属性按在 \(F\) 中只出现在左、只出现在右、两边都出现、都不出现分类(L / R / LR / N);若由 L 与 N 组成的集合的闭包已是全部属性,则它常是唯一候选码(充分条件)。更复杂情形见教材算法。
-
逻辑蕴含¶
已知 \(F\) 中有 \(A \to B\)、\(B \to C\),则 \(A \to C\) 也被 \(F\) 蕴含。为判断与求码,常用 Armstrong 公理(自反、增广、传递)及推出的合并、伪传递、分解等规则;属性集闭包 \(X^+\):从 \(X\) 出发反复用 \(F\) 扩张,直到不能再加属性为止,用于判断 \(X \to Y\) 是否被 \(F\) 蕴含、求候选码等。
蕴含与闭包怎么用
设 \(F = \{\text{学号} \to \text{系号},\ \text{系号} \to \text{系名}\}\),由传递律可得 学号 → 系名 被 \(F\) 蕴含。求 \(\{\text{学号}\}^+\)。
先有学号 → 加入系号 → 再加入系名 → 闭包不再扩大,故 \(\{\text{学号}\}^+ = \{\text{学号}, \text{系号}, \text{系名}\}\)。若全属性集 \(U\) 就这三列,则 {学号} 可作为候选码(是否最小还要与别的依赖一起核对)。
最小依赖集¶
右部单属性、无冗余依赖、左部无多余属性;与 \(F\) 等价但更精简,便于分解与理解。
Example
若原始依赖里既有 学号 → 系号, 系号(右部合并),又可推出冗余的 学号 → 系名,最小依赖集会拆成右部单属性、删掉能由其他依赖推出的那一条,得到与原来等价但更短的集合,方便后面做模式分解。
范式(1NF ~ BCNF,提要)¶
-
1NF:每个属性都是不可再分的基本项(表中无「表」、无多值格直接塞一格)。
违反 1NF
地址 一栏存成「省|市|街道」混在一格但应用里当结构用,或一格塞多个电话号(多值),都不是规范的 1NF;应拆成列或拆成子表/关联表。
-
2NF:在 1NF 基础上,每个非主属性对任一候选码都是完全函数依赖(不能只靠码的一部分)。
对照 2NF
选课(学号, 课号, 姓名, 成绩),码 (学号, 课号):成绩 完全依赖码;姓名 只依赖 学号 → 对码是部分依赖 → 不满足 2NF。把 姓名 挪到 学生 表即可。
-
3NF:在 2NF 基础上,非主属性不传递依赖于任一候选码。
对照 3NF
学生(学号, 系号, 系名),码为 学号:系名 通过 系号 传递依赖于 学号。把系信息拆成 系(系号, 系名),学生表只留 学号、系号,即消去传递依赖。
-
BCNF:更严——任一非平凡 \(X \to Y\) 中,决定因子 \(X\) 都必须包含某个候选码(通俗说:「谁决定谁」里的决定方都应是码级)。
满足 BCNF 则必满足 3NF;反之不一定。Example
「学生、系、系主任、课号、成绩」若码为 (学号, 课号),则系、系主任部分依赖于码 → 最高 1NF,拆成选课(学号,课号,成绩)与学生-系等可往 2NF、3NF 走。
-
STJ(S,T,J),语义下可有 \(T \to J\) 而 \(T\) 不含码 → 3NF 可满足但非 BCNF(课件例)。
STJ 为何卡在 BCNF
设 (S, J) 为候选码(每位学生一门课只由一个教师教),又有 教师 → 教室。决定方 教师 不是超码,却决定了 教室 → 违反 BCNF。拆成 SJT(S,T,J) 与 TJ(T,J) 等形式可消除这类依赖。
-
4NF(了解):消除非平凡多值依赖中决定方不是超码的情况;与「一对多」独立信息叠在一张表里产生的冗余有关(如医生–患者–电话例)。
4NF 直觉
若 医生 与 患者、医生 与 电话 是两路独立的多对多/一对多信息,硬塞进一张宽表会产生多值依赖冗余;拆成只表达一种多值联系的表,才便于达到 4NF。
-
范式层级(了解包含关系即可):\(\text{5NF} \subseteq \text{4NF} \subseteq \text{BCNF} \subseteq \text{3NF} \subseteq \text{2NF} \subseteq \text{1NF}\)。
设计时记三句话¶
-
一事一地:尽量让一个关系只表达一个实体或一种联系。
Example
「课程信息」与「学生选课」分表:课程表管课号、课名、学分;选课表只放学号、课号、成绩——查课名时做一次连接即可,但各表职责清晰。
-
分解不是越碎越好:要兼顾 无损连接 与 保持函数依赖,并权衡查询是否要频繁多表连接。
Example
把每个非主属性都拆成单列表会连接爆炸;若某分解不保持原有函数依赖,还得在应用层额外约束,容易出错。
-
实务上:能到 BCNF 最好;若到 BCNF 会丢失某些依赖,有时退而取 3NF 以保持依赖与无损。
Example
教材中常见结论:3NF 分解可无损且保持依赖;BCNF 可无损但不一定能保持全部依赖。若业务强依赖某条 FD,而 BCNF 分解把它弄「丢」了,可能保留 3NF 更稳妥。