初识关系数据理论

1. “关系模式” 的定义

“关系模式” 可以理解成数据库中 “表的设计蓝图”,它的专业写法是 R(U,D,DOM,F),但其实不用记这么复杂,核心看这几个部分:

  • R:表的名字(比如 “学生表”);

  • U:这张表包含的字段集合(比如学生表的 “学号、姓名、学院”);

  • F:字段之间的约束规则(比如 “学号确定了,姓名就确定了”);(剩下的DDOM是字段的取值范围,和设计关系不大,所以后面简化成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)→ SnoCno是主属性;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 表拆成两张表:

  1. SC 表:存 “学号 + 课程号 + 成绩”

    • 主键:(Sno, Cno)

    • 依赖关系:成绩完全依赖 “学号 + 课程号”→ 满足 2NF;

  2. 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,没员工就无法插入 “市场部 + 市场部地址” 的信息;

  • 删除异常:如果研发部的最后一个员工离职,删除该员工记录时,“研发部 + 研发部地址” 的信息也会被一起删掉。

解决方式:拆分表消除传递依赖

把 “员工表” 拆成两张表:

  1. 员工 - 部门表:

    • 字段:员工ID(主键)姓名部门名称

    • 依赖关系:部门名称直接依赖员工ID→ 无传递依赖;

  2. 部门 - 地址表:

    • 字段:部门名称(主键)部门地址

    • 依赖关系:部门地址直接依赖部门名称→ 无传递依赖。

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 拆成两张表:

  1. ST(S,T):存 “学生 + 教师”,码是(S,T)

  2. 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(成绩),已知的函数依赖关系(即业务规则)构成集合FF = { 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→YY→Z,则X→Z(传递依赖)。实例:已知Sno→School(学号→学院)、School→Sloc(学院→宿舍),则Sno→Sloc(学号→宿舍)。

6.3、推论(由公理推导的 3 条实用规则)

1. 合并规则

规则:若X→YX→Z,则X→YZ(把两个依赖合并)。实例:已知Sno→SchoolSno→Sloc,则Sno→(School,Sloc)(学号→学院 + 宿舍)。

2. 伪传递规则

规则:若X→YWY→Z,则XW→Z(“伪” 是因为多了个 W)。实例:已知Sno→School(学号→学院)、(School,Cno)→Grade(学院 + 课程号→成绩),则(Sno,Cno)→Grade(学号 + 课程号→成绩)。

3. 分解规则

规则:若X→YZ,则X→YX→Z(把合并的依赖拆开)。实例:已知Sno→(School,Sloc),则Sno→SchoolSno→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 推导:

  1. Sno→School,得到School

  2. School→Sloc,得到Sloc

  3. 没有更多依赖能推导了。

所以 \(Sno_F^+ = \{Sno, School, Sloc\}\)(即 “学号能决定的所有属性”)。

例 2:求 “学号 + 课程号 (Sno,Cno)” 的闭包\((Sno,Cno)_F^+\)

(Sno,Cno)出发,通过 F 推导:

  1. Sno→School,得到School

  2. School→Sloc,得到Sloc

  3. (Sno,Cno)→Grade,得到Grade

所以 \((Sno,Cno)_F^+ = \{Sno, Cno, School, Sloc, Grade\}\)(即 “学号 + 课程号能决定所有属性”)。

7.4闭包的核心作用

闭包是个 “工具”,用来解决两个实际问题:

  1. 判断候选码:如果某个属性集的闭包包含所有字段,且它的任何子集的闭包不包含所有字段,那它就是候选码(比如上面的(Sno,Cno));

  2. 判断函数依赖是否成立:如果要判断 “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左边SnoX(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左边SchoolX(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 点:

  1. 右边只有一个属性:比如不能是Sno→School,Mname,得拆成Sno→SchoolSno→Mname

  2. 没有多余依赖:去掉任何一个依赖,剩下的依赖集就和原集不等价;

  3. 左边不能再简化:比如(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→SchoolSchool→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 是最小依赖集

  1. 右边都是单个属性→ 满足条件①;

  2. 去掉任何一个依赖(比如去掉School→Mname),就推不出Sno→Mname了→ 没有多余依赖,满足条件②;

  3. 左边都不能再简化→ 满足条件③。

总结

定理 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)都能 “开所有锁”,且不能再简化(比如去掉BA的闭包不够)→ 候选码是(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验证效果

  1. 保持函数依赖:子表的依赖集合并后({Sno→School, School→Mname}),和原 F 等价→ 没丢规则;

  2. 满足 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总结

算法的步骤可简化为:

  1. 建初始表(填a/b);

  2. 按依赖更新表(把b改成a);

  3. 看是否有全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 的核心是按最小依赖集分组生成子表,步骤如下:

  1. 确认 F 是最小依赖集:

    • 右边都是单属性(符合);

    • 无多余依赖(去掉任何一个依赖都无法推导原 F);

    • 左边无冗余属性(SnoSchool(Sno,Cno) 都不能简化)。

  2. 按依赖左边分组,生成子表:

    • 依赖 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}

  3. 算法 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) 的所有属性(SnoCno);

  • 结论:码表R*是冗余的,可以删除。

11.3、算法 6.4 的最终拆分结果

τ = {R₁(Sno,School), R₂(School,Mname), R₃(Sno,Cno,Grade)}

11.4、验证最终结果是否满足 3 个目标

验证目标

验证过程

结论

保持函数依赖

子表依赖集合并为 {Sno→School, School→Mname, (Sno,Cno)→Grade},与原 F 等价

✅ 满足

所有子表符合 3NF

R₁:School 直接依赖 Sno,无传递 / 部分依赖R₂:Mname 直接依赖 School,无传递 / 部分依赖R₃:Grade 直接依赖 (Sno,Cno),无传递 / 部分依赖

✅ 满足

无损连接

用算法 6.3(表格法)验证,会出现全a

✅ 满足

11.5核心逻辑总结

本质是 “算法 6.2 + 码表补丁”:

  1. 先用算法 6.2 保证 “保持依赖 + 3NF”;

  2. 再用码表保证 “无损连接”;

  3. 最后删除冗余码表,得到精简的理想拆分结果。

十二、(分解法)转换为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→YX 都必须是候选码。

先判断原表 R 是否满足 BCNF:

函数依赖

左边 X 是否是候选码

是否满足 BCNF

Sno→School

否(Sno 不是候选码)

❌ 不满足

School→Mname

否(School 不是候选码)

❌ 不满足

(Sno,Cno)→Grade

是(本身就是候选码)

✅ 满足

结论:原表 R 不满足 BCNF,需要用算法 6.5 拆分。

12.3、算法的完整步骤(结合实例)

算法 6.5 是递归拆分的过程,步骤如下:

步骤 1:初始化拆分集合

令拆分集合 ρ = {R}(初始只有原表)。

步骤 2:检查 ρ 中是否有不满足 BCNF 的表

遍历 ρ 中的表,发现 R 不满足 BCNF,选择其中一个不满足 BCNF 的依赖,比如 Sno→School

步骤 3:对不满足 BCNF 的表进行拆分

根据依赖 X→Y(这里 X=SnoY=School),将表 R 拆分为两个子表:

  1. R₁ = X ∪ Y = {Sno, School},依赖集 F₁ = {Sno→School}

  2. R₂ = U - Y = {Sno, Mname, Cno, Grade},依赖集 F₂ = {Sno→Mname, (Sno,Cno)→Grade}(由原依赖推导:Sno→School + School→Mname → Sno→Mname

此时拆分集合更新为 ρ = {R₁, R₂}

步骤 4:递归检查并拆分新生成的表

  1. 检查 R₁ 是否满足 BCNF

    • R₁ 的依赖是 Sno→School

    • 求 R₁ 的候选码:Sno(闭包是 {Sno, School}

    • 判断:依赖 Sno→School 的左边 Sno 是候选码 → R₁ 满足 BCNF。

  2. 检查 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、关键验证

  1. 是否满足 BCNF:所有子表都满足 BCNF 的判断标准 → ✅ 是。

  2. 是否无损连接:算法 6.5 的拆分过程基于函数依赖,天然保证无损连接 → ✅ 是。

  3. 是否保持函数依赖:原依赖 School→Mname 在拆分后的子表中消失了(没有子表同时包含 School 和 Mname)→ ❌ 否。

12.6、算法 6.5 的核心总结

核心要点

具体说明

目标

拆分到所有子表满足 BCNF + 无损连接

方法

递归找不满足 BCNF 的依赖,按 X→Y 拆分为 X∪Y 和 U-Y

代价

可能丢失原有的函数依赖

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值