1. “关系模式” 的定义
“关系模式” 可以理解成数据库中 “表的设计蓝图”,它的专业写法是 R(U,D,DOM,F),但其实不用记这么复杂,核心看这几个部分:
-
R:表的名字(比如 “学生表”); -
U:这张表包含的字段集合(比如学生表的 “学号、姓名、学院”); -
F:字段之间的约束规则(比如 “学号确定了,姓名就确定了”);(剩下的D和DOM是字段的取值范围,和设计关系不大,所以后面简化成R(U,F))
2. 最基础的设计要求:第一范式(1NF)
所有表都要满足一个最低要求:表中的每个单元格,只能存 “不可拆分” 的数据。比如 “学生信息” 字段不能存 “张三 | 信息学院”(因为可以拆成姓名、学院),必须拆成单独的字段 —— 满足这个要求的表,就是 “第一范式”。
3. 核心概念:数据依赖(重点是 “函数依赖”)
“数据依赖” 是表中字段之间的约束关系(比如 “学号→姓名”),是现实业务规则的体现。其中最常用的是 “函数依赖”:
-
可以类比成数学里的函数
y=f(x):x确定了,y就唯一确定了。 -
比如 “学号→姓名”:学号确定了,对应的姓名就唯一确定(一个学号只对应一个学生);
-
再比如 “学号→学院”:学号确定了,对应的学院也唯一确定(一个学生只属于一个学院)。
举例:
假设学校想做一个 “学生教务表”,直接把所有信息塞到一张表里,字段是:学号(Sno)、学院(School)、院长(Mname)、课程号(Cno)、成绩(Grade)。
这张表的 “约束规则”是:
-
学号→学院:一个学号只对应一个学院(比如学号 S1 的学生,肯定只在 “信息学院”);
-
学院→院长:一个学院只有一个院长(比如 “信息学院” 的院长是张明);
-
(学号,课程号)→成绩:一个学生选某门课,只有一个成绩(比如 S1 选 C1 课,成绩是 95)。
这张表会出啥问题?
咱们用 “信息学院有 5 个学生,都选了 C1 课” 的场景来看:
1. 数据冗余(浪费空间)
这 5 个学生的 “学院(信息学院)”“院长(张明)” 会重复 5 次(因为每个学生的每条选课记录都要写一遍)。相当于一句话重复写 5 遍,白占了很多存储空间。
2. 更新异常(改数据要改一堆,容易出错)
如果信息学院换院长了(比如张明换成李华),那这 5 个学生的选课记录里的 “院长” 字段,得手动改 5 次。只要漏改 1 次,就会出现 “信息学院同时有两个院长” 的矛盾。
3. 插入异常(想存的信息存不进去)
如果学校新开了一个 “人工智能学院”,但还没招学生,那 “人工智能学院 + 院长” 的信息根本没法存到这张表里 —— 因为这张表必须填 “学号”,但新学院还没学生,没学号可填。
4. 删除异常(想删的信息删了,不该删的也没了)
如果信息学院的学生全部毕业,要删掉这些学生的记录,那 “信息学院 + 院长张明” 的信息也会被一起删掉 —— 以后想查 “信息学院的院长是谁”,这张表就查不到了。
总结一下
这两页的核心是说:把所有信息堆在一张表里的设计,是 “不好的设计”—— 会导致数据重复、改数据麻烦、存 / 删信息出问题。
码:(码、主码、候选码、全码、超码、外码、主属性、非主属性)
一、核心概念:码(候选码)
定义:能唯一确定整个表中一行数据的 “属性 / 属性组合”,且这个组合是 “最小的”(不能再删字段,删了就不能唯一确定)。
实例 1:学生表(Student)
表字段:Sno(学号), Sname(姓名), Ssex(性别), Sbirthdate(生日), Smajor(专业)
-
分析:
Sno(学号)能唯一确定一行(一个学号对应一个学生),且不需要其他字段→Sno是候选码。
实例 2:学生选课表(SC)
表字段:Sno(学号), Cno(课程号), Grade(成绩)
-
分析:单独
Sno不能唯一确定一行(一个学生选多门课),单独Cno也不行(一门课有多个学生);但(Sno,Cno)组合能唯一确定一行(一个学生选一门课对应一个成绩),且不能再删字段→(Sno,Cno)是候选码。
实例 3:全码(极端情况)
关系模式R(P,W,A)(P = 演奏者,W = 作品,A = 听众)
-
业务规则:一个演奏者演多部作品,一部作品被多个演奏者演,听众可欣赏不同组合;
-
分析:只有
(P,W,A)三个字段一起,才能唯一确定一行(比如 “演奏者 P1 演作品 W1,听众 A1 欣赏”)→ 整个属性组是码,称为全码。
二、超码
定义:能唯一确定一行数据,但不是 “最小组合”(可以删字段,删了仍可能是码)。
实例:学生选课表(SC)
-
候选码是
(Sno,Cno); -
(Sno,Cno,Grade)也能唯一确定一行,但Grade是多余的(删了Grade,(Sno,Cno)还是码)→(Sno,Cno,Grade)是超码。
三、主属性 vs 非主属性
-
主属性:包含在任何一个候选码里的属性;
-
非主属性:不包含在任何候选码里的属性。
实例:学生选课表(SC)
候选码是(Sno,Cno)→ Sno、Cno是主属性;Grade不包含在候选码里→ Grade是非主属性。
四、外码
定义:一个表中的属性 / 属性组,不是自己表的码,但却是另一个表的码。
实例:学生选课表(SC)的Sno
-
SC 表中,
Sno不是自己的码(SC 的码是(Sno,Cno)); -
但
Sno是 “学生表(Student)” 的码;→Sno是 SC 表的外码(用来关联 SC 表和 Student 表)。
总结
-
码(候选码):最小的 “唯一确定行” 的属性组合(如学生表的
Sno、选课表的(Sno,Cno)); -
超码:包含候选码的属性组合(比候选码 “冗余”);
-
主属性:候选码里的属性;
-
外码:关联两个表的 “跨表码”。
规范化:(自变量指向因变量,自变量决定因变量,因变量依赖于自变量)
如果一个关系模式包含一些不合适的数据依赖,这样的关系模式会存在数据冗余、更新异常、插入异常、删除异常的问题。
规范化理论要讨论的内容:通过分解关系模式来消除其中不合适的数据依赖,以解决插入异常、删除异常、更新异常和数据冗余问题。
1.函数依赖分类理解(函数依赖(包括平凡 / 非平凡、完全 / 部分、传递):完全是针对同一个表内的字段。)
一、先明确基础:函数依赖 vs 多值依赖
-
函数依赖:像 “学号→姓名”—— 一个学号唯一对应一个姓名(
x确定,y唯一确定)。 -
多值依赖:比如 “课程→{教师,教材}”—— 一门课可以对应多个教师、多本教材(
x确定,y可以有多个值)。
二、平凡函数依赖 vs 非平凡函数依赖
核心区别:依赖的字段是否是被依赖字段的 “子集”。
1. 平凡函数依赖
如果依赖的字段(因变量)是被依赖字段(自变量)的 “子集”,就是平凡的。
-
实例:已知字段集合是 “学号、姓名”,则 “(学号,姓名)→学号” 是平凡函数依赖 —— 因为 “学号” 本身就是 “(学号,姓名)” 的一部分,只要前面的字段确定,后面的字段必然确定。
2. 非平凡函数依赖
如果依赖的字段不是被依赖字段的 “子集”,就是非平凡的(真正有意义的依赖)。
-
实例:“学号→姓名”——“姓名” 不是 “学号” 的子集,学号确定后姓名才确定,这是有实际业务意义的依赖。
三、完全函数依赖 vs 部分函数依赖
核心区别:依赖是否需要 “所有字段一起” 才能确定,还是 “其中一个字段就能单独确定”。
1. 完全函数依赖(完全函数依赖一定是非平凡函数依赖)(针对 “非主属性” 和 “候选码” 的关系 —— 非主属性必须依赖整个候选码,不能依赖候选码的某个子集。非主属性必须完全函数依赖于每一个候选码)
必须所有字段组合在一起,才能确定被依赖的字段(少一个字段都不行)。
实例 1:学生选课表(SC)
表结构:Sno(学号), Cno(课程号), Grade(成绩)
-
候选码:
(Sno, Cno)(学号 + 课程号,唯一确定一行); -
非主属性:
Grade(成绩); -
依赖关系:单独
Sno无法确定Grade(一个学生选多门课,成绩不同);单独Cno也无法确定Grade(一门课有多个学生,成绩不同);只有(Sno, Cno)组合在一起,才能唯一确定Grade。
→ 这里Grade完全函数依赖于候选码(Sno, Cno)。
实例 2:反例(部分函数依赖)
如果表结构是:Sno(学号), Cno(课程号), Sname(姓名), Grade(成绩)
-
候选码:
(Sno, Cno); -
非主属性:
Sname(姓名)、Grade(成绩); -
依赖关系:
Sname只依赖Sno(学号确定,姓名就确定),不需要Cno→ 这是部分函数依赖;Grade依赖(Sno, Cno)→ 这是完全函数依赖。
2. 部分函数依赖(可平凡可非平凡)
字段组合中的某一个字段,就能单独确定被依赖的字段(不需要所有字段)。
-
实例:字段组合 “(学号,课程号)→姓名”—— 虽然写了 “学号 + 课程号”,但其实单独用 “学号” 就能确定姓名(学号→姓名),“课程号” 是多余的,这就是部分函数依赖。
四、传递函数依赖
核心:A→B,B→C,且 B 不能反过来确定 A,那么 A→C 就是传递依赖(A 通过 B 间接确定 C)。
-
实例:要构成传递函数依赖,得同时满足 3 个条件:
-
A → B(A 能确定 B);
-
B → C(B 能确定 C);
-
B 不能反过来确定 A(B → A 不成立);此时,A → C 就是传递函数依赖。
-
Sno → School:学号能确定学院(比如学号 S1 对应 “信息学院”)→ 满足条件 1;
-
School → Sloc:学院能确定宿舍(比如 “信息学院” 的学生都住 “1 号楼”)→ 满足条件 2;
-
School → Sno 不成立:一个学院有多个学生,学院不能确定学号(比如 “信息学院” 对应 S1、S2、S3 等多个学号)→ 满足条件 3;
五、1NF、2NF、3NF、BCNF
5.1NF举例
第一范式(1NF)是数据库设计的最基础要求,核心规则是:表中的每个单元格(字段的每个值),必须是 “不可拆分的原子数据”,不能是组合值、多值或嵌套结构。
我用 “学生信息表” 的正反例来解释:
反例:不满足 1NF 的表
假设设计了一张 “学生表”,其中 “联系方式” 字段存的是 “电话 + 地址” 的组合值:
|
学号 |
姓名 |
联系方式(电话 + 地址) |
|---|---|---|
|
S1 |
张三 |
138xxxx1234,北京市朝阳区 |
|
S2 |
李四 |
139xxxx5678,上海市浦东新区 |
这个表不满足 1NF,因为 “联系方式” 字段的内容是 “电话 + 地址” 的组合值,可以拆分成 “电话” 和 “地址” 两个独立字段 —— 它不是 “不可拆分的原子数据”。
正例:满足 1NF 的表
把 “联系方式” 拆成 “电话” 和 “地址” 两个独立字段,每个单元格都是不可拆分的原子数据:
|
学号 |
姓名 |
电话 |
地址 |
|---|---|---|---|
|
S1 |
张三 |
138xxxx1234 |
北京市朝阳区 |
|
S2 |
李四 |
139xxxx5678 |
上海市浦东新区 |
这个表满足 1NF,因为每个字段的每个值都是 “不能再拆分的最小数据单元”(电话就是单纯的号码,地址就是单纯的地址)。
再举一个多值的反例
如果 “选课” 字段存多个课程号(多值),同样不满足 1NF:
|
学号 |
姓名 |
选课(多门课) |
|---|---|---|
|
S1 |
张三 |
C1,C2,C3 |
拆分后满足 1NF 的表(拆成 “学生 - 选课” 关联表):
|
学号 |
课程号 |
|---|---|
|
S1 |
C1 |
|
S1 |
C2 |
|
S1 |
C3 |
一句话总结
1NF 的核心是 “字段值不能拆”—— 只要表中没有 “组合值、多值、嵌套值”,每个单元格都是最小的原子数据,就满足第一范式。
2.2NF举例
2.1先明确 2NF 的核心要求
要满足 2NF,得先满足 1NF(每个单元格不可拆分),再满足:表中所有 “非主属性”(不是主键的字段),必须完全函数依赖于 “整个主键”(不能只依赖主键的一部分)。
2.2举反例:S-L-C 表为啥不满足 2NF?
1. 先看 S-L-C 表的结构
表名:S-L-C字段:学号 (Sno)、学院 (School)、宿舍 (Sloc)、课程号 (Cno)、成绩 (Grade)主键(码):(Sno, Cno)(学号 + 课程号,因为一个学生选一门课对应一个成绩)
2. 看字段间的依赖关系
-
成绩 (Grade):必须同时依赖 “学号 + 课程号”(一个学生选一门课才有一个成绩)→ 完全函数依赖(符合 2NF 要求);
-
学院 (School):只依赖 “学号 (Sno)”(一个学号对应一个学院),不需要课程号→ 部分函数依赖(依赖主键的一部分);
-
宿舍 (Sloc):只依赖 “学号 (Sno)”(一个学号对应一个学院,一个学院对应一个宿舍)→ 部分函数依赖(依赖主键的一部分)。
3.不满足 2NF 会出啥问题?
-
插入异常:想插入一个 “还没选课的学生”(只有学号、学院、宿舍,没有课程号),但主键是 “学号 + 课程号”,课程号为空就插不进去;
-
删除异常:学生 S4 只选了 C3 这门课,若删除 C3 的选课记录,连 S4 的学院、宿舍信息也会被一起删掉;
-
修改复杂:学生转学院(比如从人工智能学院转信息学院),因为他选了 k 门课,学院、宿舍字段会重复存 k 次,得改 k 条记录,漏改就会出错。
2.3怎么解决?拆分表,让非主属性 “完全依赖主键”
把 S-L-C 表拆成两张表:
-
SC 表:存 “学号 + 课程号 + 成绩”
-
主键:(Sno, Cno)
-
依赖关系:成绩完全依赖 “学号 + 课程号”→ 满足 2NF;
-
-
S-L 表:存 “学号 + 学院 + 宿舍”
-
主键:Sno(学号)
-
依赖关系:学院、宿舍完全依赖 “学号”→ 满足 2NF。
-
总结
S-L-C 表因为 “学院、宿舍只依赖主键的一部分(学号)”,不满足 2NF,导致插入 / 删除 / 修改异常;把表拆成 SC 和 S-L 后,每张表的非主属性都 “完全依赖整个主键”,就满足 2NF 了。
3.3NF
3.1、先明确 3NF 的核心要求
要满足 3NF,得先满足 2NF,再额外满足:表中所有 “非主属性”,不能传递函数依赖于主键(即不能通过 “中间属性” 间接依赖主键)。
3.2、举反例
1. 表结构
表名:员工表字段:员工ID(主键)、姓名、部门名称、部门地址主键:员工ID
2. 字段依赖关系
-
部门名称:直接依赖员工ID(一个员工对应一个部门)→ 直接依赖; -
部门地址:不是直接依赖员工ID,而是通过 “部门名称” 间接依赖:员工ID→部门名称→部门地址→ 传递函数依赖(违反 3NF)。
3. 不满足 3NF 的问题
-
修改麻烦:如果 “研发部” 的地址从 “1 楼” 改成 “2 楼”,因为每个研发部员工的记录里都存了 “部门地址”,得把所有研发部员工的 “部门地址” 都改一遍,漏改就会出现 “研发部同时有 1 楼、2 楼地址” 的矛盾;
-
插入异常:想新增一个 “还没招员工的部门(比如市场部)”,因为主键是
员工ID,没员工就无法插入 “市场部 + 市场部地址” 的信息; -
删除异常:如果研发部的最后一个员工离职,删除该员工记录时,“研发部 + 研发部地址” 的信息也会被一起删掉。
解决方式:拆分表消除传递依赖
把 “员工表” 拆成两张表:
-
员工 - 部门表:
-
字段:
员工ID(主键)、姓名、部门名称 -
依赖关系:
部门名称直接依赖员工ID→ 无传递依赖;
-
-
部门 - 地址表:
-
字段:
部门名称(主键)、部门地址 -
依赖关系:
部门地址直接依赖部门名称→ 无传递依赖。
-
4.BCNF
4.1、先明确 BCNF 的核心定义
BCNF 是比 3NF 更严格的范式,核心要求是:关系模式中,任何一个 “决定因素”(能决定其他属性的字段 / 字段组合),都必须是 “码(主键 / 候选键)”。
简单说:只要一个字段 / 组合能决定其他属性,它就必须是整个表的主键(不能是 “非码的决定因素”)。
4.2、先看 “满足 BCNF 的例子”(理解 BCNF 的标准)
以例 6.7 的SJP(S,J,P)为例:
-
字段:学生 (S)、课程 (J)、名次 (P);
-
依赖关系:
(S,J)→P(学生 + 课程→名次)、(J,P)→S(课程 + 名次→学生); -
码(候选键):
(S,J)和(J,P)都是码; -
分析:所有 “决定因素”(
(S,J)、(J,P))都是码→ 满足 BCNF。
4.3、重点看 “不满足 BCNF 的反例(STJ 表)”(核心理解)
1. STJ 表的结构与依赖
-
表名:
STJ(S,T,J)(学生 S、教师 T、课程 J); -
业务规则:① 一个教师只教一门课(
T→J);② 一个学生选一门课,对应固定教师((S,J)→T);③ 一个学生选一个教师,对应固定课程((S,T)→J); -
码(候选键):
(S,J)、(S,T)(这两个组合能唯一确定整个表的行);
2. 为什么 STJ 满足 3NF,但不满足 BCNF?
-
满足 3NF:表中没有 “非主属性对码的传递 / 部分依赖”(所有属性都是主属性,因为码是
(S,J)和(S,T),S、T、J 都是主属性); -
不满足 BCNF:存在 “决定因素不是码” 的情况 ——
T→J中,“T(教师)” 是决定因素,但 T 不是码(码是(S,J)或(S,T)),违反了 BCNF “所有决定因素都是码” 的要求。
3. 不满足 BCNF 的问题
比如想新增 “教师 T1 教课程 J1”,但还没有学生选这门课:
-
因为表的码是
(S,J)或(S,T),必须填学生 S,否则插不进数据→ 插入异常;再比如删除 “学生 S1 选 T1 的记录”,会把 “T1 教 J1” 的信息也删掉→ 删除异常。
4.4、怎么解决?拆分表满足 BCNF
把 STJ 拆成两张表:
-
ST(S,T):存 “学生 + 教师”,码是(S,T); -
TJ(T,J):存 “教师 + 课程”,码是T;
拆分后:
-
ST中,决定因素(S,T)是码; -
TJ中,决定因素T是码;所有决定因素都是码→ 满足 BCNF,消除了异常。
总结
BCNF 是 3NF 的强化版:3NF 解决 “非主属性的依赖问题”,BCNF 解决 “主属性的依赖问题”—— 要求所有决定因素都是码。STJ 表因为 “教师 T 是决定因素但不是码”,不满足 BCNF;拆分后,每张表的决定因素都是码,就满足 BCNF 了。
5.大总结:
5.1、各范式核心总结
|
范式 |
核心要求(基于前一范式) |
解决的问题 |
通俗理解 |
|---|---|---|---|
|
1NF |
表中每个单元格是 “不可拆分的原子数据” |
字段值拆分混乱(比如 “电话 + 地址” 存在一个字段里) |
字段值不能拆,是最小数据单元 |
|
2NF |
非主属性必须完全函数依赖于整个主键(不能依赖主键的一部分) |
部分依赖导致的插入 / 删除 / 修改异常(比如 “学号 + 课程号” 为主键,“学院” 只依赖学号) |
非主属性要 “绑死整个主键”,不能只绑主键的一部分 |
|
3NF |
非主属性不能传递函数依赖于主键(不能通过中间属性间接依赖) |
传递依赖导致的冗余和异常(比如 “学号→学院→宿舍”) |
非主属性要 “直接依赖主键”,不能绕弯 |
|
BCNF |
所有 “决定因素”(能决定其他属性的字段 / 组合)必须是码(主键 / 候选键) |
主属性的依赖问题(比如 “教师 T→课程 J”,但 T 不是码) |
只要能决定其他属性,它就得是整个表的主键 |
5.2、用 “学生 - 课程 - 学院” 的例子对比
以 “最原始的大表” 到 “逐步拆分满足各范式” 的过程为例:
1. 原始表(不满足 1NF)
|
学号 |
姓名 |
联系方式(电话 + 地址) |
学院 + 宿舍 |
课程号 |
成绩 |
|---|---|---|---|---|---|
|
S1 |
张三 |
138xxx, 北京朝阳 |
信息学院 1 号楼 |
C1 |
95 |
→ 问题:“联系方式”“学院 + 宿舍” 是组合值,不满足 1NF。
2. 满足 1NF 的表(拆分组合字段)
|
学号 |
姓名 |
电话 |
地址 |
学院 |
宿舍 |
课程号 |
成绩 |
|---|---|---|---|---|---|---|---|
|
S1 |
张三 |
138xxx |
北京朝阳 |
信息学院 |
1 号楼 |
C1 |
95 |
→ 满足 1NF,但主键是 “学号 + 课程号”,“学院、宿舍” 只依赖学号(部分依赖)→ 不满足 2NF。
3. 满足 2NF 的表(拆分部分依赖)
拆成 2 张表:
-
SC 表(学号,课程号,成绩):成绩完全依赖 “学号 + 课程号”;
-
S 表(学号,姓名,电话,地址,学院,宿舍):学院、宿舍完全依赖 “学号”。
→ 满足 2NF,但 S 表中 “学号→学院→宿舍”(传递依赖)→ 不满足 3NF。
4. 满足 3NF 的表(消除传递依赖)
把 S 表再拆成 2 张:
-
S - 学院表(学号,姓名,电话,地址,学院):学院直接依赖学号;
-
学院 - 宿舍表(学院,宿舍):宿舍直接依赖学院。
→ 满足 3NF,但如果存在 “教师表(教师 T, 课程 J)”,若 “T→J” 但 T 不是码→ 不满足 BCNF。
5. 满足 BCNF 的表(所有决定因素都是码)
若教师表原始设计:
|
教师 T |
课程 J |
学生 S |
|---|---|---|
|
T1 |
J1 |
S1 |
→ “T→J” 但 T 不是码(主键是 “T+S”),不满足 BCNF;拆分后:
-
教师 - 课程表(教师 T, 课程 J):主键是 T(T→J,T 是码);
-
教师 - 学生表(教师 T, 学生 S):主键是 “T+S”。
→ 满足 BCNF。
5.3、核心关系
范式是 “逐步严格” 的:1NF ← 2NF(消除部分依赖) ← 3NF(消除传递依赖) ← BCNF(消除主属性依赖)(箭头表示 “基于前者,更严格”)
越往后的范式,表的设计越合理,数据冗余和异常越少。
六、Armstrong公理系统
6.1、先明确基础:什么是 “函数依赖集 F”?
假设我们有一个表,字段是Sno(学号)、School(学院)、Sloc(宿舍)、Cno(课程号)、Grade(成绩),已知的函数依赖关系(即业务规则)构成集合F:F = { Sno→School, School→Sloc, (Sno,Cno)→Grade }
6.2、Armstrong 公理(3 条基础规则)
这是推导 “隐含函数依赖” 的依据,相当于数学里的 “定理”。
1. 自反律(A1)
规则:若Y是X的子集,则X→Y(必然成立)。实例:
-
因为
Sno是(Sno,Cno)的子集,所以(Sno,Cno)→Sno(学号 + 课程号能确定学号); -
这是 “废话式依赖”,但逻辑上必然成立。
2. 增广律(A2)
规则:若X→Y,则XZ→YZ(给依赖的两边同时加一个字段 Z,依赖仍成立)。实例:已知Sno→School(学号→学院),给两边加Cno,则(Sno,Cno)→(School,Cno)(学号 + 课程号→学院 + 课程号)。
3. 传递律(A3)
规则:若X→Y且Y→Z,则X→Z(传递依赖)。实例:已知Sno→School(学号→学院)、School→Sloc(学院→宿舍),则Sno→Sloc(学号→宿舍)。
6.3、推论(由公理推导的 3 条实用规则)
1. 合并规则
规则:若X→Y且X→Z,则X→YZ(把两个依赖合并)。实例:已知Sno→School、Sno→Sloc,则Sno→(School,Sloc)(学号→学院 + 宿舍)。
2. 伪传递规则
规则:若X→Y且WY→Z,则XW→Z(“伪” 是因为多了个 W)。实例:已知Sno→School(学号→学院)、(School,Cno)→Grade(学院 + 课程号→成绩),则(Sno,Cno)→Grade(学号 + 课程号→成绩)。
3. 分解规则
规则:若X→YZ,则X→Y且X→Z(把合并的依赖拆开)。实例:已知Sno→(School,Sloc),则Sno→School、Sno→Sloc。
七、属性集的闭包(核心工具)
闭包(这里指属性集的闭包)是关系数据库理论里的一个实用概念,简单说就是:从某个属性 / 属性组合出发,通过已知的函数依赖,能 “决定” 的所有属性的集合。
7.1生活类比:什么是 “闭包”?
比如你有一把 “家门钥匙”,用它能打开:
-
家门;
-
家门里的抽屉(因为开了家门才能开抽屉);
-
抽屉里的钱包(因为开了抽屉才能拿钱包)。
这里,“家门钥匙” 的闭包就是 “家门、抽屉、钱包”—— 即 “用这把钥匙能得到的所有东西”。
7.2数据库里的 “属性集闭包”
在数据库中,“属性集 X 的闭包(记为\(X_F^+\))”= 从 X 出发,通过已知的函数依赖集 F,能推导出的所有属性的集合。
7.3实例:
假设我们有:
-
字段:
学号(Sno)、学院(School)、宿舍(Sloc)、成绩(Grade) -
已知函数依赖集
F = { Sno→School, School→Sloc, (Sno,Cno)→Grade }
例 1:求 “学号 (Sno)” 的闭包\(Sno_F^+\)
从Sno出发,通过 F 推导:
-
由
Sno→School,得到School; -
由
School→Sloc,得到Sloc; -
没有更多依赖能推导了。
所以 \(Sno_F^+ = \{Sno, School, Sloc\}\)(即 “学号能决定的所有属性”)。
例 2:求 “学号 + 课程号 (Sno,Cno)” 的闭包\((Sno,Cno)_F^+\)
从(Sno,Cno)出发,通过 F 推导:
-
由
Sno→School,得到School; -
由
School→Sloc,得到Sloc; -
由
(Sno,Cno)→Grade,得到Grade;
所以 \((Sno,Cno)_F^+ = \{Sno, Cno, School, Sloc, Grade\}\)(即 “学号 + 课程号能决定所有属性”)。
7.4闭包的核心作用
闭包是个 “工具”,用来解决两个实际问题:
-
判断候选码:如果某个属性集的闭包包含所有字段,且它的任何子集的闭包不包含所有字段,那它就是候选码(比如上面的
(Sno,Cno)); -
判断函数依赖是否成立:如果要判断 “X→Y” 是否成立,只需看 Y 是不是 X 闭包的子集(比如 “\(Sno→Sloc\)” 成立,因为
Sloc在\(Sno_F^+\)里)。
一句话总结
闭包就是 “从 X 出发,能‘管到’的所有属性”—— 是推导属性关系的核心工具。
7.5 求闭包的算法
先明确已知条件
-
所有字段(属性集 U):
{Sno(学号), School(学院), Sloc(宿舍), Cno(课程号), Grade(成绩)} -
函数依赖集 F:
{ Sno→School, School→Sloc, (Sno,Cno)→Grade } -
要计算的属性集 X:
(Sno, Cno)(学号 + 课程号)
按照算法步骤逐步计算
步骤 1:初始化
令 X(0) = X = {Sno, Cno},i=0。
步骤 2:求 B(找能推导的属性)
B 的定义:从 F 中找 “左边是 X (i) 子集” 的依赖,把这些依赖右边的属性收集起来。
遍历 F 中的依赖:
-
依赖 1:
Sno→School左边Sno是X(0)={Sno,Cno}的子集 → 右边的School加入 B; -
依赖 2:
School→Sloc左边School不是X(0)的子集 → 不加入; -
依赖 3:
(Sno,Cno)→Grade左边(Sno,Cno)是X(0)的子集 → 右边的Grade加入 B;
所以,B = {School, Grade}。
步骤 3:更新 X (i+1)
X(i+1) = B ∪ X(i) = {School, Grade} ∪ {Sno, Cno} = {Sno, Cno, School, Grade}。
步骤 4:判断是否终止
比较X(1)和X(0):X(1)≠X(0),且X(1)≠U(U 是所有字段,还差 Sloc)→ 不终止。
步骤 5:迭代计算(i=1)
i 更新为 1,回到步骤 2:
-
现在
X(1) = {Sno, Cno, School, Grade}; -
遍历 F 中的依赖:
-
依赖 1:
Sno→School→ 已处理; -
依赖 2:
School→Sloc左边School是X(1)的子集 → 右边的Sloc加入 B; -
依赖 3:
(Sno,Cno)→Grade→ 已处理;
-
所以,新的B = {Sloc}。
步骤 6:再次更新 X (i+1)
X(2) = B ∪ X(1) = {Sloc} ∪ {Sno, Cno, School, Grade} = {Sno, Cno, School, Sloc, Grade}。
步骤 7:再次判断是否终止
比较X(2)和X(1):X(2)≠X(1),但X(2)=U(包含所有字段)→ 算法终止。
最终结果
属性集X=(Sno,Cno)关于 F 的闭包是:X_F^+ = {Sno, Cno, School, Sloc, Grade}
一句话总结
这个算法的核心是 “从 X 出发,不断收集能推导的属性,直到没有新属性可加,或包含所有字段”—— 像 “滚雪球” 一样,把 X 能决定的所有属性都 “滚” 进来。
7.6 用闭包判断候选码
因为某属性或属性的集合的闭包是所有字段,且它的子集(比如 Sno)的闭包≠所有字段,所以(Sno,Cno)是候选码。
八、最小函数依赖集
8.1先明确 “最小依赖集” 的 3 个条件
最小依赖集是 “精简到不能再精简” 的函数依赖集,必须满足 3 点:
-
右边只有一个属性:比如不能是
Sno→School,Mname,得拆成Sno→School和Sno→Mname; -
没有多余依赖:去掉任何一个依赖,剩下的依赖集就和原集不等价;
-
左边不能再简化:比如
(Sno,School)→School可以简化成Sno→School(左边的School是多余的)。
2.1、判断 “无多余依赖”(即:去掉任意一个依赖,依赖集不再等价)
验证方法:逐个移除依赖,检查剩余依赖集是否能推导出被移除的依赖。若不能推导,则该依赖非多余;若能推导,则该依赖多余。
1. 验证依赖 \(AB→C\) 是否多余
-
临时移除 \(AB→C\),剩余依赖集 \(F' = \{DE→C, B→D\}\);
-
计算 AB 关于 \(F'\) 的闭包 \((AB)^+\):\((AB)^+ = \{A,B\} \xrightarrow{B→D} \{A,B,D\}\)(无法推导 C);
-
结论:\(AB→C\) 不能被剩余依赖推导,非多余。
2. 验证依赖 \(DE→C\) 是否多余
-
临时移除 \(DE→C\),剩余依赖集 \(F' = \{AB→C, B→D\}\);
-
计算 DE 关于 \(F'\) 的闭包 \((DE)^+\):\((DE)^+ = \{D,E\}\)(无法推导 C);
-
结论:\(DE→C\) 不能被剩余依赖推导,非多余。
3. 验证依赖 \(B→D\) 是否多余
-
临时移除 \(B→D\),剩余依赖集 \(F' = \{AB→C, DE→C\}\);
-
计算 B 关于 \(F'\) 的闭包 \(B^+\):\(B^+ = \{B\}\)(无法推导 D);
-
结论:\(B→D\) 不能被剩余依赖推导,非多余。
因此,原依赖集 F 无多余依赖。
3.1、判断 “左边无多余属性”(即:依赖左边的任意属性不能被去掉,否则依赖不成立)
验证方法:对每个依赖的左边属性集,逐个移除属性,检查剩余属性集是否能推导出右边属性。若不能推导,则该属性非多余;若能推导,则该属性多余。
1. 验证依赖 \(AB→C\) 的左边 AB
-
移除 A,剩余左边 B,计算 B 的闭包 \(B^+ = \{B,D\}\)(无法推导 C);
-
移除 B,剩余左边 A,计算 A 的闭包 \(A^+ = \{A\}\)(无法推导 C);
-
结论:\(A、B\) 均非多余,左边无多余属性。
2. 验证依赖 \(DE→C\) 的左边 DE
-
移除 D,剩余左边 E,计算 E 的闭包 \(E^+ = \{E\}\)(无法推导 C);
-
移除 E,剩余左边 D,计算 D 的闭包 \(D^+ = \{D\}\)(无法推导 C);
-
结论:\(D、E\) 均非多余,左边无多余属性。
3. 验证依赖 \(B→D\) 的左边 B
-
左边是单属性 B,无属性可移除;
-
结论:左边无多余属性。
综上,原依赖集 F 满足 “无多余依赖” 和 “左边无多余属性”,且右边都是单属性,因此它本身就是最小依赖集。
8.2实例:从 “冗余依赖集 F'” 到 “最小依赖集 F”
假设我们有一个冗余的函数依赖集 F':F' = { Sno→School, Sno→Mname, School→Mname, (Sno,Cno)→Grade, (Sno,School)→School }
步骤 1:把右边拆成单个属性(对应定理 6.3 的①)
F' 里的依赖右边已经是单个属性,这一步不用动。
步骤 2:删除多余依赖(对应定理 6.3 的②)
逐个检查每个依赖,看去掉它后,剩下的依赖集是否还能推出它:
-
检查
Sno→Mname:剩下的依赖集里有Sno→School和School→Mname,通过传递律能推出Sno→Mname→ 所以Sno→Mname是多余依赖,可以删掉; -
检查
(Sno,School)→School:这是 “自反律” 的平凡依赖(左边包含右边),本身就是多余的→ 可以删掉;
删除后,依赖集变成:F1 = { Sno→School, School→Mname, (Sno,Cno)→Grade }
步骤 3:简化左边的属性(对应定理 6.3 的③)
检查每个依赖的左边,看是否能去掉某个属性,同时保持依赖成立:
-
检查
(Sno,Cno)→Grade:去掉Sno,剩下Cno→Grade?不行(一门课有多个成绩);去掉Cno,剩下Sno→Grade?不行(一个学生有多个成绩)→ 左边不能简化; -
其他依赖的左边都是单个属性,不用简化。
最终得到的最小依赖集 F
F = { Sno→School, School→Mname, (Sno,Cno)→Grade }
8.3验证 F 是最小依赖集
-
右边都是单个属性→ 满足条件①;
-
去掉任何一个依赖(比如去掉
School→Mname),就推不出Sno→Mname了→ 没有多余依赖,满足条件②; -
左边都不能再简化→ 满足条件③。
总结
定理 6.3 的 “极小化处理” 就是 3 步:拆右边→删多余依赖→简化左边,最终得到的依赖集就是 “最小依赖集”—— 它和原依赖集等价,但没有任何冗余。
九、找候选码
9.1先搞懂 “属性分类”:像给钥匙零件贴标签
把表的属性分成 4 类(方便判断哪些能当 “候选码的零件”):
-
L 类:只出现在依赖的左边(比如 “学号” 只在 “学号→学院” 的左边)→ 是 “必须带的零件”;
-
R 类:只出现在依赖的右边(比如 “成绩” 只在 “学号 + 课程号→成绩” 的右边)→ 是 “不能当钥匙的零件”;
-
N 类:依赖的左右都没出现→ 是 “必须带的零件”;
-
LR 类:依赖的左右都出现(比如 “学院” 既在 “学号→学院” 左边,又在 “学院→宿舍” 右边)→ 是 “可选零件”。
9.2核心定理:哪些零件能当钥匙?
-
L 类、N 类必须出现在候选码里(比如 “学号” 是 L 类,候选码里一定有它);
-
R 类不能出现在候选码里(比如 “成绩” 是 R 类,候选码里肯定没有它)。
9.3实例:手把手找候选码
已知:
-
属性 U:
{A,B,C,D,E} -
依赖 F:
{AB→C, B→D, C→E, EC→B, AC→B}
步骤 1:给属性分类
-
L 类:只有
A(只在依赖左边出现); -
R 类:只有
D(只在依赖右边出现); -
N 类:无;
-
LR 类:
B、C、E(依赖左右都出现)。
步骤 2:先拿 “必须带的零件”(L 类 + N 类)
这里只有A,先看A的闭包(A能开多少锁):A的闭包=A(只能开自己)→ 不够,得加 “可选零件(LR 类)”。
步骤 3:加 LR 类零件,试闭包
给A加 LR 类的B,试(A,B)的闭包:
-
由
AB→C,得C; -
由
C→E,得E; -
由
B→D,得D;→(A,B)的闭包=ABCDE(能开所有锁)。
再给A加 LR 类的C,试(A,C)的闭包:
-
由
AC→B,得B; -
由
B→D,得D; -
由
C→E,得E;→(A,C)的闭包=ABCDE(能开所有锁)。
步骤 4:确定候选码
(A,B)和(A,C)都能 “开所有锁”,且不能再简化(比如去掉B,A的闭包不够)→ 候选码是(A,B)和(A,C)。
总结
找候选码的思路是:先拿必须带的零件(L/N 类)→ 试闭包够不够→ 不够就加可选零件(LR 类)→ 直到闭包包含所有属性。
保持函数依赖的模式分解
一、先理解 “保持函数依赖”:拆分表不能丢规则
比如原表有规则 “学院→院长”,拆分后如果没有任何一张子表包含 “学院” 和 “院长”,这个规则就丢了 —— 后续操作会出问题(比如改院长时,不知道对应哪个学院)。
保持函数依赖的分解:拆分后的子表,要能通过合并推导出原表的所有函数依赖(不能丢规则)。
二、实例引入:拆分丢规则的反面教材
原表R(Sno, School, Mname),函数依赖F={Sno→School, School→Mname}(规则:学号→学院,学院→院长)。
错误拆分:拆成R1(Sno, School)和R2(Sno, Mname)。
-
问题:原规则
School→Mname丢了(R1、R2 都不同时包含 School 和 Mname)→ 无法保证 “一个学院对应一个院长”,会出现数据矛盾(比如同一学院在不同记录里对应不同院长)。
十、“合成法” 拆分表(保持函数依赖 + 满足 3NF)
9.1拆分表的标准流程,能同时实现:①保持函数依赖;②每个子表满足 3NF。
我们用 原表R(Sno, School, Mname),F={Sno→School, School→Mname}来演示步骤:
步骤 1:对 F 做 “极小化处理”(简化依赖集)
原 F 已经是最小依赖集(右边单属性、无多余依赖、左边不能简化),不用动。
步骤 2:处理 “没出现在 F 中的属性”
原表所有属性(Sno、School、Mname)都在 F 里,所以这一步跳过。
步骤 3:检查是否有 “X→A 且 XA=U”
即是否有一个依赖能覆盖所有属性。原 F 里的依赖都做不到(比如Sno→School只覆盖 Sno、School),所以跳过。
步骤 4:按 “依赖左边相同” 分组,生成子表
把 F 中左边相同的依赖分到一组,每组的属性构成一个子表:
-
第一组:
Sno→School→ 左边是 Sno,对应子表R1(Sno, School),依赖集F1={Sno→School}; -
第二组:
School→Mname→ 左边是 School,对应子表R2(School, Mname),依赖集F2={School→Mname}。
最终拆分结果
拆分后得到两个子表:
-
R1(Sno, School)(满足 3NF,依赖 Sno→School); -
R2(School, Mname)(满足 3NF,依赖 School→Mname)。
9.2验证效果
-
保持函数依赖:子表的依赖集合并后(
{Sno→School, School→Mname}),和原 F 等价→ 没丢规则; -
满足 3NF:R1 中 School 直接依赖 Sno;R2 中 Mname 直接依赖 School→ 都无部分 / 传递依赖,满足 3NF。
9.3总结
算法 6.2 的核心是 “把最小依赖集按左边分组,每组生成一个子表”,这样既不会丢原有的函数依赖,又能让每个子表满足 3NF,是拆分表的 “标准安全流程”。
十、算法验证无损性
10.1
步骤 1:构建初始表格
表格的列对应原表属性,行对应拆分的子表,填写规则:
-
子表包含的属性→ 填
aₖ(k是属性的序号,如 A 是a₁); -
子表不包含的属性→ 填
bᵢⱼ(i是行号,j是列号,如 R₁的 D 列填b₁₄)。
例 6.15 的初始表:
|
子表(行) |
A(列 1) |
B(列 2) |
C(列 3) |
D(列 4) |
E(列 5) |
|---|---|---|---|---|---|
|
R₁ |
a₁ |
a₂ |
a₃ |
b₁₄ |
b₁₅ |
|
R₂ |
b₂₁ |
b₂₂ |
a₃ |
a₄ |
b₂₅ |
|
R₃ |
b₃₁ |
b₃₂ |
b₃₃ |
a₄ |
a₅ |
步骤 2:按函数依赖逐个更新表格
遍历F中的每个依赖,更新表格中 “依赖右边属性” 的符号:
处理依赖 1:AB→C
-
找
AB列(列 1、列 2)中符号完全相同的行:R₁的 AB 是a₁a₂,R₂是b₂₁b₂₂,R₃是b₃₁b₃₂→ 没有相同行,表格不变。
处理依赖 2:C→D
-
找
C列(列 3)中符号相同的行:R₁和 R₂的 C 列都是a₃→ 这两行是 “待更新行”; -
看这两行的
D列(列 4):R₂的 D 列是a₄(有a),所以把 R₁的 D 列b₁₄改成a₄。
更新后表格:
|
子表 |
A |
B |
C |
D |
E |
|---|---|---|---|---|---|
|
R₁ |
a₁ |
a₂ |
a₃ |
a₄ |
b₁₅ |
|
R₂ |
b₂₁ |
b₂₂ |
a₃ |
a₄ |
b₂₅ |
|
R₃ |
b₃₁ |
b₃₂ |
b₃₃ |
a₄ |
a₅ |
处理依赖 3:D→E
-
找
D列(列 4)中符号相同的行:R₁、R₂、R₃的 D 列都是a₄→ 这三行是 “待更新行”; -
看这三行的
E列(列 5):R₃的 E 列是a₅(有a),所以把 R₁的b₁₅、R₂的b₂₅都改成a₅。
最终更新后表格:
|
子表 |
A |
B |
C |
D |
E |
|---|---|---|---|---|---|
|
R₁ |
a₁ |
a₂ |
a₃ |
a₄ |
a₅ |
|
R₂ |
b₂₁ |
b₂₂ |
a₃ |
a₄ |
a₅ |
|
R₃ |
b₃₁ |
b₃₂ |
b₃₃ |
a₄ |
a₅ |
步骤 3:判断是否无损连接
检查表格是否有某一行全是a:
-
R₁对应的行是
a₁、a₂、a₃、a₄、a₅(全a)→ 说明这个拆分是无损连接。
为什么 “全 a 行” 代表无损连接?
“全 a 行” 意味着:这一行对应的子表,通过依赖能推导出原表的所有属性—— 因此把所有子表合并时,能完整还原原表的数据,不会丢失或混乱。
10.2总结
算法的步骤可简化为:
-
建初始表(填
a/b); -
按依赖更新表(把
b改成a); -
看是否有全
a行→ 有则无损,无则有损。
十一、转换为3NF既有无损连接性又保持函数依赖的分解:
11.1实例
我们用一个完整的学生 - 学院 - 课程关系模式来演示:
-
原关系模式
R(U,F)-
属性集
U = {Sno(学号), School(学院), Mname(院长), Cno(课程号), Grade(成绩)} -
函数依赖集
F = {Sno→School, School→Mname, (Sno,Cno)→Grade} -
候选码(通过属性闭包计算):
(Sno,Cno)(因为(Sno,Cno)的闭包包含所有属性)
-
11.2算法的完整步骤(结合实例)
分为 5 个核心步骤,每一步都有明确的目的:
步骤 1:执行算法 6.2,得到 “保持函数依赖 + 3NF” 的子表集合
算法 6.2 的核心是按最小依赖集分组生成子表,步骤如下:
-
确认 F 是最小依赖集:
-
右边都是单属性(符合);
-
无多余依赖(去掉任何一个依赖都无法推导原 F);
-
左边无冗余属性(
Sno、School、(Sno,Cno)都不能简化)。
-
-
按依赖左边分组,生成子表:
-
依赖
Sno→School→ 子表R₁(Sno, School),依赖集F₁={Sno→School}; -
依赖
School→Mname→ 子表R₂(School, Mname),依赖集F₂={School→Mname}; -
依赖
(Sno,Cno)→Grade→ 子表R₃(Sno,Cno,Grade),依赖集F₃={(Sno,Cno)→Grade}。
-
-
算法 6.2 的结果:子表集合
ρ = {R₁, R₂, R₃}。这个集合保持函数依赖、所有子表满足 3NF,但暂时无法保证无损连接。
步骤 2:确定原关系模式 R 的候选码
通过属性闭包计算:
-
计算
(Sno,Cno)的闭包:(Sno,Cno)⁺ = {Sno,Cno,School,Mname,Grade}(包含所有属性); -
验证子集:
Sno⁺={Sno,School,Mname}、Cno⁺={Cno},都不包含所有属性; -
结论:候选码
X = (Sno,Cno)。
步骤 3:构造码表 R*(X, Fₓ)
码表的作用是让拆分集合包含原表的候选码,从而保证无损连接,构造规则:
-
X:必须是原表的候选码 → 这里X=(Sno,Cno); -
Fₓ:候选码X自身的函数依赖集(仅包含左右都在X中的依赖,例如AB→A);原依赖集F中没有依赖的左右都在(Sno,Cno)里 →Fₓ=∅(空集)。 -
最终码表:
R*(Sno,Cno),依赖集F*=∅。
步骤 4:将码表加入步骤 1 的子表集合,得到临时拆分集合 τ
临时集合是算法 6.2 的结果 + 码表:τ = {R₁(Sno,School), R₂(School,Mname), R₃(Sno,Cno,Grade), R*(Sno,Cno)}
步骤 5:简化临时集合 τ,去掉冗余的码表
简化规则:如果临时集合中已有子表包含码表的所有属性,则删除码表。
-
检查子表:
R₃(Sno,Cno,Grade)包含码表R*(Sno,Cno)的所有属性(Sno和Cno); -
结论:码表
R*是冗余的,可以删除。
11.3、算法 6.4 的最终拆分结果
τ = {R₁(Sno,School), R₂(School,Mname), R₃(Sno,Cno,Grade)}
11.4、验证最终结果是否满足 3 个目标
|
验证目标 |
验证过程 |
结论 |
|---|---|---|
|
保持函数依赖 |
子表依赖集合并为 |
✅ 满足 |
|
所有子表符合 3NF |
R₁:School 直接依赖 Sno,无传递 / 部分依赖R₂:Mname 直接依赖 School,无传递 / 部分依赖R₃:Grade 直接依赖 (Sno,Cno),无传递 / 部分依赖 |
✅ 满足 |
|
无损连接 |
用算法 6.3(表格法)验证,会出现全 |
✅ 满足 |
11.5核心逻辑总结
本质是 “算法 6.2 + 码表补丁”:
-
先用算法 6.2 保证 “保持依赖 + 3NF”;
-
再用码表保证 “无损连接”;
-
最后删除冗余码表,得到精简的理想拆分结果。
十二、(分解法)转换为BCNF的无损连接分解
12.1实例准备
-
原关系模式
R(U,F)-
属性集
U = {Sno, School, Mname, Cno, Grade} -
函数依赖集
F = {Sno→School, School→Mname, (Sno,Cno)→Grade} -
候选码:
(Sno,Cno)(闭包包含所有属性)
-
12.2、先明确 BCNF 的判断标准
一个关系模式满足 BCNF,当且仅当:对于每一个非平凡函数依赖 X→Y,X 都必须是候选码。
先判断原表 R 是否满足 BCNF:
|
函数依赖 |
左边 |
是否满足 BCNF |
|---|---|---|
|
|
否( |
❌ 不满足 |
|
|
否( |
❌ 不满足 |
|
|
是(本身就是候选码) |
✅ 满足 |
结论:原表 R 不满足 BCNF,需要用算法 6.5 拆分。
12.3、算法的完整步骤(结合实例)
算法 6.5 是递归拆分的过程,步骤如下:
步骤 1:初始化拆分集合
令拆分集合 ρ = {R}(初始只有原表)。
步骤 2:检查 ρ 中是否有不满足 BCNF 的表
遍历 ρ 中的表,发现 R 不满足 BCNF,选择其中一个不满足 BCNF 的依赖,比如 Sno→School。
步骤 3:对不满足 BCNF 的表进行拆分
根据依赖 X→Y(这里 X=Sno,Y=School),将表 R 拆分为两个子表:
-
R₁ = X ∪ Y = {Sno, School},依赖集F₁ = {Sno→School} -
R₂ = U - Y = {Sno, Mname, Cno, Grade},依赖集F₂ = {Sno→Mname, (Sno,Cno)→Grade}(由原依赖推导:Sno→School+School→Mname→Sno→Mname)
此时拆分集合更新为 ρ = {R₁, R₂}。
步骤 4:递归检查并拆分新生成的表
-
检查
R₁是否满足 BCNF-
R₁的依赖是Sno→School -
求
R₁的候选码:Sno(闭包是{Sno, School}) -
判断:依赖
Sno→School的左边Sno是候选码 →R₁满足 BCNF。
-
-
检查
R₂是否满足 BCNF-
R₂的依赖是Sno→Mname、(Sno,Cno)→Grade -
求
R₂的候选码:(Sno,Cno)(闭包包含{Sno, Mname, Cno, Grade}) -
判断:依赖
Sno→Mname的左边Sno不是候选码 →R₂不满足 BCNF。
继续拆分
R₂,选择不满足的依赖Sno→Mname:-
拆分为
R₂₁ = {Sno, Mname},依赖集F₂₁ = {Sno→Mname} -
拆分为
R₂₂ = {Sno, Cno, Grade},依赖集F₂₂ = {(Sno,Cno)→Grade}
此时拆分集合更新为
ρ = {R₁, R₂₁, R₂₂}。 -
步骤 5:再次递归检查,直到所有表满足 BCNF
-
检查
R₂₁:依赖Sno→Mname,候选码是Sno→ 满足 BCNF。 -
检查
R₂₂:依赖(Sno,Cno)→Grade,候选码是(Sno,Cno)→ 满足 BCNF。
至此,所有子表都满足 BCNF,拆分结束。
12.4、算法 6.5 的最终拆分结果
ρ = {R₁(Sno,School), R₂₁(Sno,Mname), R₂₂(Sno,Cno,Grade)}
12.5、关键验证
-
是否满足 BCNF:所有子表都满足 BCNF 的判断标准 → ✅ 是。
-
是否无损连接:算法 6.5 的拆分过程基于函数依赖,天然保证无损连接 → ✅ 是。
-
是否保持函数依赖:原依赖
School→Mname在拆分后的子表中消失了(没有子表同时包含School和Mname)→ ❌ 否。
12.6、算法 6.5 的核心总结
|
核心要点 |
具体说明 |
|---|---|
|
目标 |
拆分到所有子表满足 BCNF + 无损连接 |
|
方法 |
递归找不满足 BCNF 的依赖,按 |
|
代价 |
可能丢失原有的函数依赖 |
760

被折叠的 条评论
为什么被折叠?



