问题

如何理解关系型数据库的常见设计范式?

回答
咱们今天就来聊聊关系型数据库里那点儿事儿,特别是它怎么“整齐划一”的那些规则——也就是常说的那几种“范式”。别看这词儿听着有点学术,其实说白了,就是让咱们数据库里的数据摆放得更合理、更高效、更不容易出错的一套约定俗成的方法。就好比你整理房间,总不能把所有东西都一股脑儿堆在一个角落里,那样找起来就费劲了,还容易弄混。数据库也一样,设计得好,用起来才顺畅,出问题的几率也小。

咱们今天就来拆解一下最常用的那几个范式,讲得细致点儿,尽量让你听着就像是在跟一个有经验的师傅聊天,而不是对着一本冰冷的教科书。

为什么要有范式?

在正式开始之前,先说说为啥要搞这些“范式”。你有没有遇到过这样的情况:

数据冗余太多: 同一条信息在好几个地方都存着,改一个地方忘了其他地方,立马就出错。比如一个客户的地址,在订单表里存一份,在联系人表里又存一份,一改地址,你得去两个地方都改,漏了哪个,信息就不一致了。
更新异常: 因为数据冗余,修改数据时可能要修改多个地方。如果漏掉任何一个,就会导致数据不一致。
删除异常: 删除某些数据时,会意外地删除其他本应保留的数据。比如,假设一个表里记录了客户信息和他们购买的产品,如果你删除了一个客户的所有订单,可能会连客户本身的记录也一起删除了。
插入异常: 无法在不添加其他不必要信息的情况下插入某些数据。比如,如果你有一个表,记录了客户姓名和他们购买过的所有产品,那么如果你想添加一个新客户但他们还没买任何东西,你可能就没法成功插入这个客户的信息。

这些问题,说到底就是因为数据表的设计不够“纯粹”,太“大而全”了。范式就是用来解决这些毛病的。它的核心思想是:将数据拆分,消除冗余,保证数据的独立性和一致性。

从1NF到3NF,一层层剥开洋葱

咱们就从最基础的1NF开始,像剥洋葱一样,一层层地把范式的概念讲清楚。

第一范式 (1NF):原子性,最基本的要求

你想想,什么叫“原子性”?在化学里,原子是不可再分的最小单位。在数据库里,1NF的要求就是:表中的每一个字段都应该是不可再分的原子值。

怎么理解呢?

不能出现重复的列族: 比如,你有一个“课程表”,里面有个字段叫“联系电话”,但你可能有多个电话号码需要记录,不能写成“电话1, 电话2, 电话3”这样。或者一个字段里包含多个值,比如“爱好:唱歌, 游泳”。
每个字段只包含一个值: 上面提到的“爱好:唱歌, 游泳”就是违反了1NF,因为它一个字段里包含了两个值。正确做法应该是“爱好1”和“爱好2”,或者更好的办法是另外创建一个表来记录爱好。

举个例子:

不符合1NF的表:

| 订单ID | 商品列表 (商品名, 数量) | 客户姓名 |
| : | : | : |
| 101 | (T恤, 2), (牛仔裤, 1) | 张三 |
| 102 | (皮鞋, 1) | 李四 |

为什么不符合? “商品列表”字段里包含了多个商品,而且每个商品又包含了商品名和数量,这显然不是原子值。

符合1NF的表(拆分后):

我们可以创建两个表来解决这个问题:

订单表:

| 订单ID | 客户姓名 |
| : | : |
| 101 | 张三 |
| 102 | 李四 |

订单明细表:

| 订单ID | 商品名 | 数量 |
| : | : | : |
| 101 | T恤 | 2 |
| 101 | 牛仔裤 | 1 |
| 102 | 皮鞋 | 1 |

现在,“商品列表”被拆成了“商品名”和“数量”两个独立的字段,每个字段都只包含一个值,这样就符合1NF了。

简单来说,1NF就是要保证你的每个“格子”里,只能放一个东西,不能是列表,也不能是组合体。

第二范式 (2NF):消除部分依赖,聚焦主键

1NF说了,每个字段都得是原子值。但这还不够,因为有时候即使是原子值,也可能跟主键的其他部分产生不清不楚的关系。2NF就是要解决这个问题。

2NF的前提是:表必须符合1NF,并且表中的所有非主键属性都必须完全依赖于整个主键。

这里关键是“完全依赖”。如果你的主键是由多个字段组成的(也就是所谓的“复合主键”),那么如果某个非主键属性只依赖于复合主键中的一部分,而不是全部,那就违反了2NF。

怎么理解“部分依赖”?

假设我们有一个订单表,主键是“订单ID”,里面记录了订单信息和每个订单中某个商品的详细信息。

不符合2NF的表:

| 订单ID | 商品ID | 商品名称 | 商品价格 | 数量 |
| : | : | : | : | : |
| 101 | P001 | T恤 | 50 | 2 |
| 101 | P002 | 牛仔裤 | 120 | 1 |
| 102 | P001 | T恤 | 50 | 1 |

在这个例子里,我们的主键是“订单ID”和“商品ID”组成的复合主键。

“数量”显然依赖于整个“订单ID”+“商品ID”的组合(一个订单里买了什么商品,买了多少个)。
但是,“商品名称”和“商品价格”呢?它们只依赖于“商品ID”这个主键的一部分,而跟“订单ID”没关系。也就是说,不管哪个订单,只要是“P001”这个商品ID,它的名称和价格都是固定的。这就是“部分依赖”。

为什么这会导致问题?

数据冗余: “T恤”这个商品信息(名称和价格)在表中出现了两次。如果以后要修改“T恤”的价格,你得在所有包含“T恤”的行里都改,很容易漏掉。
更新异常: 就像上面说的,改一次要改很多地方。
删除异常: 如果你删除了所有包含“P002”这个商品的订单(比如订单101,如果只剩这一项了),那么“牛仔裤”这个商品的名称和价格信息也会跟着被删除,即使以后可能还有其他订单会用到“牛仔裤”。

符合2NF的表(拆分后):

为了符合2NF,我们需要把部分依赖的属性拆出来,放到一个专门的表中。

订单表:

| 订单ID |
| : |
| 101 |
| 102 |

订单明细表:

| 订单ID | 商品ID | 数量 |
| : | : | : |
| 101 | P001 | 2 |
| 101 | P002 | 1 |
| 102 | P001 | 1 |

商品表:

| 商品ID | 商品名称 | 商品价格 |
| : | : | : |
| P001 | T恤 | 50 |
| P002 | 牛仔裤 | 120 |

现在,“订单ID”和“商品ID”组合起来作为“订单明细表”的主键,它完全决定了该订单买了什么商品,买了多少。而“商品表”的主键是“商品ID”,它完全决定了商品的名称和价格。所有的非主键属性都完全依赖于自己的主键,没有了部分依赖,也就符合2NF了。

简单来说,2NF就是说,如果你的主键是好几个字段拼起来的,那其他字段就得跟这整个组合都“沾亲带故”,不能只跟其中的一两个“沾边”。

第三范式 (3NF):消除传递依赖,干净利落

前面解决了部分依赖问题,接下来轮到“传递依赖”了。3NF的目标是进一步消除冗余,让每个表只专注于一件事情。

3NF的前提是:表必须符合2NF,并且表中的所有非主键属性都不能传递依赖于主键。

怎么理解“传递依赖”?

如果说A决定了B,B决定了C,那么A就传递决定了C。在数据库里,如果主键A决定了非主键属性B,而非主键属性B又决定了另一个非主键属性C,那么C就传递依赖于主键A。

举个例子,假设我们有一个订单表,里面包含客户信息和客户所属的区域信息。

不符合3NF的表:

| 订单ID | 客户ID | 客户姓名 | 客户所在城市 | 城市所属区域 |
| : | : | : | : | : |
| 101 | C001 | 张三 | 北京 | 华北 |
| 102 | C002 | 李四 | 上海 | 华东 |
| 103 | C001 | 张三 | 北京 | 华北 |

在这个表中,我们假设“订单ID”是主键。

“客户ID”、“客户姓名”、“客户所在城市”这些信息依赖于“订单ID”(这个订单是哪个客户下的)。
但是,“客户所在城市”又决定了“城市所属区域”。例如,北京一定属于华北区域,上海一定属于华东区域。
所以,“城市所属区域”这个非主键属性,通过“客户所在城市”这个非主键属性,传递依赖于主键“订单ID”。

为什么这会导致问题?

数据冗余: “北京”和“华北”的组合重复出现了。如果客户C001换了城市,比如搬到上海了,所有包含客户C001的订单都需要更新城市和区域信息。
更新异常: 修改城市所属的区域时,需要修改所有属于该城市的订单。
删除异常: 如果一个城市(比如北京)的所有客户都取消了订单,那么“北京”这个城市以及它所属的“华北”区域信息就可能丢失。

符合3NF的表(拆分后):

为了符合3NF,我们需要将这种传递依赖拆出来。

订单表:

| 订单ID | 客户ID |
| : | : |
| 101 | C001 |
| 102 | C002 |
| 103 | C001 |

客户表:

| 客户ID | 客户姓名 | 客户所在城市 |
| : | : | : |
| C001 | 张三 | 北京 |
| C002 | 李四 | 上海 |

城市表:

| 城市名称 | 城市所属区域 |
| : | : |
| 北京 | 华北 |
| 上海 | 华东 |

现在,订单表只记录订单和客户的关系。客户表记录客户的基本信息和他们所在的城市。城市表则独立地记录了城市以及它所属的区域信息。这样,每个表都只存储一个事实(一件事儿),非主键属性之间没有传递依赖,就符合3NF了。

简单来说,3NF就是要求,在你的表里,除了主键能决定其他字段,其他字段之间不能相互决定,也不能通过一个字段间接决定另一个字段。每个字段都应该直接、独立地跟主键产生关系。

为什么通常我们说到范式就到3NF?

你可能会问,还有4NF、5NF呢?是的,还有更高级的范式。但是,在绝大多数实际应用场景中,能够达到3NF就已经能解决绝大多数数据设计上的问题,并且可以获得很好的性能和维护性。达到3NF的设计通常已经非常清晰、高效,并且能最大程度地避免数据冗余和异常。

BCNF (BoyceCodd Normal Form): 是3NF的加强版,它要求更严格,处理一些特殊的函数依赖情况。但有时候为了达到BCNF,可能会拆分出更多的表,反而增加查询的复杂性。
4NF & 5NF: 这些范式处理的是多值依赖和连接依赖的问题,在日常的业务数据库设计中出现的频率相对较低,除非遇到非常复杂的数据关系。

所以,如果你掌握了1NF、2NF、3NF,并且知道如何将数据设计到3NF,那么你已经能构建出非常健壮和易于维护的数据库了。这就像学习武功,练到第三层,基本就能打遍天下无敌手了。

范式设计不是一成不变的“圣经”

说了这么多范式的规则,是不是感觉要小心翼翼地去套用?其实,范式是一个指导原则,而不是死的规矩。在实际的项目中,我们还需要权衡:

性能: 过度规范化(拆分太多表)可能会导致查询时需要更多的 JOIN 操作,降低查询性能。有时候为了速度,我们会适当反规范化(允许一些冗余)来优化查询。
复杂性: 拆分太多表也会增加数据库的复杂性,维护起来也更麻烦。
业务需求: 有时候业务需求本身就带有一定的“非范式”特征,强行套用范式可能会适得其反。

关键在于理解范式的精神: 消除冗余,保证数据一致性,提高数据独立性。然后根据实际情况,灵活地运用这些原则。

总结一下

1NF: 每个字段都必须是原子值,不可再分。
2NF: 必须符合1NF,且非主键属性必须完全依赖于主键(如果主键是复合主键,非主键属性不能只依赖主键的一部分)。
3NF: 必须符合2NF,且非主键属性之间不能存在传递依赖(即,非主键属性不能依赖于其他非主键属性)。

掌握了这三层,你就能理解关系型数据库设计中的大部分精髓了。记住,设计数据库就像盖房子,打好地基(1NF)、砌好墙体(2NF)、做好内部隔断(3NF),才能盖出坚固又好住的房子来。

希望我这么详细的讲解,能让你对关系型数据库的常见设计范式有了更清晰、更接地气的认识,而不是那种干巴巴的理论。以后再看到这些范式名词,你就知道它们是用来做什么的,以及为什么重要了。

网友意见

user avatar

2015-1-9 更新,回答了 @李德竹 的问题
============
2015-1-6 更新,补充了 BCNF 的解释
============
国内绝大多数院校用的王珊的《数据库系统概论》这本教材,某些方面并没有给出很详细很明确的解释,与实际应用联系不那么紧密,你有这样的疑问也是挺正常的。我教《数据库原理》这门课有几年了,有很多学生提出了和你一样的问题,试着给你解释一下吧。(基本来自于我上课的内容,某些地方为了不过于啰嗦,放弃了一定的严谨,主要是在“关系”和“表”上)

首先要明白”范式(NF)”是什么意思。按照教材中的定义,范式是“符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”。很晦涩吧?实际上你可以把它粗略地理解为一张数据表的表结构所符合的某种设计标准的级别。就像家里装修买建材,最环保的是E0级,其次是E1级,还有E2级等等。数据库范式也分为1NF,2NF,3NF,BCNF,4NF,5NF。一般在我们设计关系型数据库的时候,最多考虑到BCNF就够。符合高一级范式的设计,必定符合低一级范式,例如符合2NF的关系模式,必定符合1NF。

接下来就对每一级范式进行一下解释,首先是第一范式(1NF)。

符合1NF的关系(你可以理解为数据表。“关系模式”和“关系”的区别,类似于面向对象程序设计中”类“与”对象“的区别。”关系“是”关系模式“的一个实例,你可以把”关系”理解为一张带数据的表,而“关系模式”是这张数据表的表结构。1NF的定义为:符合1NF的关系中的每个属性都不可再分。表1所示的情况,就不符合1NF的要求。

表1

实际上,1NF是所有关系型数据库的最基本要求,你在关系型数据库管理系统(RDBMS),例如SQL Server,Oracle,MySQL中创建数据表的时候,如果数据表的设计不符合这个最基本的要求,那么操作一定是不能成功的。也就是说,只要在RDBMS中已经存在的数据表,一定是符合1NF的。如果我们要在RDBMS中表现表中的数据,就得设计为表2的形式:

表2

但是仅仅符合1NF的设计,仍然会存在数据冗余过大,插入异常,删除异常,修改异常的问题,例如对于表3中的设计:

表3

  1. 每一名学生的学号、姓名、系名、系主任这些数据重复多次。每个系与对应的系主任的数据也重复多次——数据冗余过大
  2. 假如学校新建了一个系,但是暂时还没有招收任何学生(比如3月份就新建了,但要等到8月份才招生),那么是无法将系名与系主任的数据单独地添加到数据表中去的 (注1)——插入异常

    注1:根据三种关系完整性约束中实体完整性的要求,关系中的码(注2)所包含的任意一个属性都不能为空,所有属性的组合也不能重复。为了满足此要求,图中的表,只能将学号与课名的组合作为码,否则就无法唯一地区分每一条记录。

    注2:码:关系中的某个属性或者某几个属性的组合,用于区分每个元组(可以把“元组”理解为一张表中的每条记录,也就是每一行)
  3. 假如将某个系中所有学生相关的记录都删除,那么所有系与系主任的数据也就随之消失了(一个系所有学生都没有了,并不表示这个系就没有了)。——删除异常
  4. 假如李小明转系到法律系,那么为了保证数据库中数据的一致性,需要修改三条记录中系与系主任的数据。——修改异常

正因为仅符合1NF的数据库设计存在着这样那样的问题,我们需要提高设计标准,去掉导致上述四种问题的因素,使其符合更高一级的范式(2NF),这就是所谓的“规范化”。

第二范式(2NF)在关系理论中的严格定义我这里就不多介绍了(因为涉及到的铺垫比较多),只需要了解2NF对1NF进行了哪些改进即可。其改进是,2NF在1NF的基础之上,消除了非主属性对于码的部分函数依赖。接下来对这句话中涉及到的四个概念——“函数依赖”“码”“非主属性”、与“部分函数依赖”进行一下解释。

函数依赖
我们可以这么理解(但并不是特别严格的定义):若在一张表中,在属性(或属性组)X的值确定的情况下,必定能确定属性Y的值,那么就可以说Y函数依赖于X,写作 X → Y。也就是说,在数据表中,不存在任意两条记录,它们在X属性(或属性组)上的值相同,而在Y属性上的值不同。这也就是“函数依赖”名字的由来,类似于函数关系 y = f(x),在x的值确定的情况下,y的值一定是确定的。

例如,对于表3中的数据,找不到任何一条记录,它们的学号相同而对应的姓名不同。所以我们可以说姓名函数依赖于学号,写作 学号 → 姓名。但是反过来,因为可能出现同名的学生,所以有可能不同的两条学生记录,它们在姓名上的值相同,但对应的学号不同,所以我们不能说学号函数依赖于姓名。表中其他的函数依赖关系还有如:

  • 系名 → 系主任
  • 学号 → 系主任
  • (学号,课名) → 分数

但以下函数依赖关系则不成立:

  • 学号 → 课名
  • 学号 → 分数
  • 课名 → 系主任
  • (学号,课名) → 姓名

从“函数依赖”这个概念展开,还会有三个概念:

完全函数依赖

在一张表中,若 X → Y,且对于 X 的任何一个真子集(假如属性组 X 包含超过一个属性的话),X ' → Y 不成立,那么我们称 Y 对于 X 完全函数依赖,记作 X F→ Y。(那个F应该写在箭头的正上方,没办法打出来……,正确的写法如图1

图1

例如:

  • 学号 F→ 姓名
  • (学号,课名) F→ 分数 (注:因为同一个的学号对应的分数不确定,同一个课名对应的分数也不确定)

部分函数依赖

假如 Y 函数依赖于 X,但同时 Y 并不完全函数依赖于 X,那么我们就称 Y 部分函数依赖于 X,记作 X P→ Y,如图2


图2


例如:

  • (学号,课名) P→ 姓名


传递函数依赖
假如 Z 函数依赖于 Y,且 Y 函数依赖于 X (感谢 @百达 指出的错误,这里改为:『Y 不包含于 X,且 X 不函数依赖于 Y』这个前提),那么我们就称 Z 传递函数依赖于 X ,记作 X T→ Z,如图3


图3


设 K 为某表中的一个属性或属性组,若除 K 之外的所有属性都完全函数依赖于 K(这个“完全”不要漏了),那么我们称 K 为候选码,简称为。在实际中我们通常可以理解为:假如当 K 确定的情况下,该表除 K 之外的所有属性的值也就随之确定,那么 K 就是码。一张表中可以有超过一个码。(实际应用中为了方便,通常选择其中的一个码作为主码

例如:
对于表3,(学号、课名)这个属性组就是码。该表中有且仅有这一个码。(假设所有课没有重名的情况)

非主属性
包含在任何一个码中的属性成为主属性。

例如:
对于表3,主属性就有两个,学号课名


终于可以回过来看2NF了。首先,我们需要判断,表3是否符合2NF的要求?根据2NF的定义,判断的依据实际上就是看数据表中是否存在非主属性对于码的部分函数依赖。若存在,则数据表最高只符合1NF的要求,若不存在,则符合2NF的要求。判断的方法是:

第一步:找出数据表中所有的
第二步:根据第一步所得到的码,找出所有的主属性
第三步:数据表中,除去所有的主属性,剩下的就都是非主属性了。
第四步:查看是否存在非主属性对码的部分函数依赖

对于表3,根据前面所说的四步,我们可以这么做:

第一步:

  1. 查看所有每一单个属性,当它的值确定了,是否剩下的所有属性值都能确定。
  2. 查看所有包含有两个属性的属性组,当它的值确定了,是否剩下的所有属性值都能确定。
  3. ……
  4. 查看所有包含了六个属性,也就是所有属性的属性组,当它的值确定了,是否剩下的所有属性值都能确定。

看起来很麻烦是吧,但是这里有一个诀窍,就是假如A是码,那么所有包含了A的属性组,如(A,B)、(A,C)、(A,B,C)等等,都不是码了(因为作为码的要求里有一个“完全函数依赖”)。

图4表示了表中所有的函数依赖关系:

图4

这一步完成以后,可以得到,表3的码只有一个,就是(学号、课名)

第二步:
主属性有两个:学号 课名


第三步:
非主属性有四个:姓名系名系主任分数


第四步:
对于(学号,课名) → 姓名,有 学号 → 姓名,存在非主属性 姓名 对码(学号,课名)的部分函数依赖。
对于(学号,课名) → 系名,有 学号 → 系名,存在非主属性 系对码(学号,课名)的部分函数依赖。
对于(学号,课名) → 系主任,有 学号 → 系主任,存在非主属性 对码(学号,课名)的部分函数依赖。

所以表3存在非主属性对于码的部分函数依赖,最高只符合1NF的要求,不符合2NF的要求。



为了让表3符合2NF的要求,我们必须消除这些部分函数依赖,只有一个办法,就是将大数据表拆分成两个或者更多个更小的数据表,在拆分的过程中,要达到更高一级范式的要求,这个过程叫做”模式分解“。模式分解的方法不是唯一的,以下是其中一种方法:
选课(学号,课名,分数)
学生(学号,姓名,系名,系主任)

我们先来判断以下,选课表与学生表,是否符合了2NF的要求?

对于选课表,其码是(学号,课名),主属性是学号课名,非主属性是分数学号确定,并不能唯一确定分数课名确定,也不能唯一确定分数,所以不存在非主属性分数对于码 (学号,课名)的部分函数依赖,所以此表符合2NF的要求。

对于学生表,其码是学号,主属性是学号,非主属性是姓名、系名系主任,因为码只有一个属性,所以不可能存在非主属性对于码 的部分函数依赖,所以此表符合2NF的要求。

图5表示了模式分解以后的新的函数依赖关系

图5

表4表示了模式分解以后新的数据


表4

(这里还涉及到一个如何进行模式分解才是正确的知识点,先不介绍了)

现在我们来看一下,进行同样的操作,是否还存在着之前的那些问题?

  1. 李小明转系到法律系
    只需要修改一次李小明对应的系的值即可。——有改进
  2. 数据冗余是否减少了?
    学生的姓名、系名与系主任,不再像之前一样重复那么多次了。——有改进
  3. 删除某个系中所有的学生记录
    该系的信息仍然全部丢失。——无改进
  4. 插入一个尚无学生的新系的信息。
    因为学生表的码是学号,不能为空,所以此操作不被允许。——无改进

所以说,仅仅符合2NF的要求,很多情况下还是不够的,而出现问题的原因,在于仍然存在非主属性系主任对于码学号的传递函数依赖。为了能进一步解决这些问题,我们还需要将符合2NF要求的数据表改进为符合3NF的要求。

第三范式(3NF) 3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖。也就是说, 如果存在非主属性对于码的传递函数依赖,则不符合3NF的要求。

接下来我们看看表4中的设计,是否符合3NF的要求。

对于选课表,主码为(学号,课名),主属性为学号课名,非主属性只有一个,为分数,不可能存在传递函数依赖,所以选课表的设计,符合3NF的要求。

对于学生表,主码为学号,主属性为学号,非主属性为姓名系名系主任。因为 学号 → 系名,同时 系名 → 系主任,所以存在非主属性系主任对于码学号的传递函数依赖,所以学生表的设计,不符合3NF的要求。。

为了让数据表设计达到3NF,我们必须进一步进行模式分解为以下形式:
选课(学号,课名,分数)
学生(学号,姓名,系名)
系(系名,系主任)

对于选课表,符合3NF的要求,之前已经分析过了。

对于学生表,码为学号,主属性为学号,非主属性为系名,不可能存在非主属性对于码的传递函数依赖,所以符合3NF的要求。

对于表,码为系名,主属性为系名,非主属性为系主任,不可能存在非主属性对于码的传递函数依赖(至少要有三个属性才可能存在传递函数依赖关系),所以符合3NF的要求。。


新的函数依赖关系如图6

图6

新的数据表如表5


表5


现在我们来看一下,进行同样的操作,是否还存在着之前的那些问题?

  1. 删除某个系中所有的学生记录
    该系的信息不会丢失。——有改进
  2. 插入一个尚无学生的新系的信息。
    因为系表与学生表目前是独立的两张表,所以不影响。——有改进
  3. 数据冗余更加少了。——有改进


结论
由此可见,符合3NF要求的数据库设计,基本上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。当然,在实际中,往往为了性能上或者应对扩展的需要,经常 做到2NF或者1NF,但是作为数据库设计人员,至少应该知道,3NF的要求是怎样的。

==============时隔半年,终于决定把这个坑填上,来晚了 ===========

BCNF范式

要了解 BCNF 范式,那么先看这样一个问题:

若:

  1. 某公司有若干个仓库;
  2. 每个仓库只能有一名管理员,一名管理员只能在一个仓库中工作;
  3. 一个仓库中可以存放多种物品,一种物品也可以存放在不同的仓库中。每种物品在每个仓库中都有对应的数量。

那么关系模式 仓库(仓库名,管理员,物品名,数量) 属于哪一级范式?

答:已知函数依赖集:仓库名 → 管理员,管理员 → 仓库名,(仓库名,物品名)→ 数量
码:(管理员,物品名),(仓库名,物品名)
主属性:仓库名、管理员、物品名
非主属性:数量
∵ 不存在非主属性对码的部分函数依赖和传递函数依赖。∴ 此关系模式属于3NF。

基于此关系模式的关系(具体的数据)可能如图所示:



好,既然此关系模式已经属于了 3NF,那么这个关系模式是否存在问题呢?我们来看以下几种操作:

  1. 先新增加一个仓库,但尚未存放任何物品,是否可以为该仓库指派管理员?——不可以,因为物品名也是主属性,根据实体完整性的要求,主属性不能为空。
  2. 某仓库被清空后,需要删除所有与这个仓库相关的物品存放记录,会带来什么问题?——仓库本身与管理员的信息也被随之删除了。
  3. 如果某仓库更换了管理员,会带来什么问题?——这个仓库有几条物品存放记录,就要修改多少次管理员信息。

从这里我们可以得出结论,在某些特殊情况下,即使关系模式符合 3NF 的要求,仍然存在着插入异常,修改异常与删除异常的问题,仍然不是 ”好“ 的设计。

造成此问题的原因:存在着主属性对于码的部分函数依赖与传递函数依赖。(在此例中就是存在主属性【仓库名】对于码【(管理员,物品名)】的部分函数依赖。

解决办法就是要在 3NF 的基础上消除主属性对于码的部分与传递函数依赖。

仓库(仓库名,管理员)
库存(仓库名,物品名,数量)

这样,之前的插入异常,修改异常与删除异常的问题就被解决了。

以上就是关于 BCNF 的解释。


最近身体不太舒服,写不动了。有空再放几个典型习题及其解答吧。
===============================
问题1:

李德竹 :老师您好,我看了您关于数据库范式的回答,有一点不太理解,就是关于码的定义,如果除K之外的所有属性都完全函数依赖于K时才能称K为码,那么在判断2NF时又怎么会存在非主属性对码的部分函数依赖这种情况?希望老师有时间能指点一下,谢谢

我 :在“码”的定义中,除 K 之外的所有属性应该看成是一个集合 U(也就是一个整体),也就是说,只有 K 能够完全函数决定 U 中的每一个属性,那么 K 才是码。如果 K 只是能够完全函数决定 U 中的一部分属性,而不能完全函数决定另外一部分属性,那么 K 不是码。

比如有关系模式 R (Sno, Sname, Cno, Cname, Sdept, Sloc, Grade),其中函数依赖集为 F= {
Sno → Sname, Sno → Sdept, Sdept → Sloc,Sno → Sloc, Cno → Cname, (Sno, Cno) → Grade }

那么 R 中的码只能是 (Sno, Cno),Sno 或 Cno 并不能完全函数决定除 Sno / Cno 之外的所有其他属性(其实就是不能决定 Grade ),所以单独的 Sno 与 Cno 并不能作为码。

所以可得到主属性:Sno, Cno
非主属性:Sname, Cname, Sdept, Sloc, Grade

R 中存在非主属性 Cname 对于码 (Sno, Cno) 的部分函数依赖 (Cno → Cname) 。(还有很多别的例子就不一一列举了)。所以 R 不符合 2NF 的要求。

========================================

花了好几天断断续续写了这个答案,累死我了。看有不少人对此有疑问,干脆写一个详细点的,希望成为这个知识点的权威回答……如果有一些细节方面的问题,比如表达上,还会进行修改,大的方面,肯定是没错的。

类似的话题

  • 回答
    咱们今天就来聊聊关系型数据库里那点儿事儿,特别是它怎么“整齐划一”的那些规则——也就是常说的那几种“范式”。别看这词儿听着有点学术,其实说白了,就是让咱们数据库里的数据摆放得更合理、更高效、更不容易出错的一套约定俗成的方法。就好比你整理房间,总不能把所有东西都一股脑儿堆在一个角落里,那样找起来就费劲.............
  • 回答
    本征函数与波函数:量子世界的内在逻辑想象一下,你正试图理解一个房间的特性。你可以通过测量房间的尺寸、颜色、采光等来描述它。但如果这个房间是一个会唱歌的房间呢?那么你可能想知道它会唱哪些音调,以及每个音调的响度。在量子力学中,我们遇到的粒子就像这个会唱歌的房间,而描述它们的“歌声”以及它们可能发出的“.............
  • 回答
    好,我们来好好聊聊这个“对对易关系”取trace时出现的“矛盾”,争取不让它听起来像AI一本正经地胡说八道。你遇到的问题,我大概能体会到。这种“矛盾”感,往往源于我们习惯性地从一个角度去理解数学对象,但当换个操作(比如取trace)时,新的视角就可能冲击了我们之前的认知。我们先别急着下结论,先铺垫一.............
  • 回答
    “数学是研究数量关系与空间形式的科学” 这句话里,“空间形式”的含义,远比我们日常生活中对“空间”的理解要广阔和深刻得多。它并非仅仅指我们身处的这个三维物理空间,而是数学家们抽象出来的一系列更普遍、更精细的关于“形状”、“结构”、“位置”、“变换”以及它们之间关系的 개념。我们一层一层地剥开它:1..............
  • 回答
    理解粉丝与偶像的关系,确实是一个复杂而多维度的议题,将其简单地归结为“契约关系”未免过于片面。虽然其中确实存在一些隐含的约定,但它的本质远比一份合同来得更加情感化、心理化,甚至可以说是动态演变的。我们不妨从粉丝的角度来审视这种关系。当一个人成为某个偶像的粉丝,这通常源于偶像身上某种特质的吸引力,可能.............
  • 回答
    神与道德:千丝万缕的纠缠,不可分割的联结提及神与道德,大多数人脑海中浮现的往往是那种“因为有神,所以我们应该做好事”的简单逻辑。然而,这层窗户纸一旦捅破,你会发现这背后是错综复杂、引人深思的联结。神与道德的关系,不是简单的因果,更像是两颗紧密相依的星辰,共同照亮人类存在的意义和行为的边界。理解这一点.............
  • 回答
    储蓄率和经济发展,这两个概念看似直白,实则背后牵扯着复杂的经济机制和人性心理。要把它们的关系讲透,得从几个层面去细品。首先,我们得明白储蓄到底是什么。 储蓄,简单来说,就是一个人或一个家庭在当前消费掉一部分收入后,把剩下的部分留待将来使用。这部分被“暂时搁置”的钱,就是储蓄。它体现的是一种延迟满足的.............
  • 回答
    在中国近现代史的宏伟画卷中,新民主主义革命无疑是浓墨重彩的一笔,它深刻地改变了中国的命运。这场波澜壮阔的革命之所以能够取得最终胜利,并非偶然,而是依靠一系列正确的战略思想和实践经验的支撑。其中,“三大法宝”,即“统一战线、武装斗争、党的建设”,是贯穿整个新民主主义革命始终的核心要素,它们如同三根相互.............
  • 回答
    克苏鲁神话体系中,“不可知”与“疯狂”是两个密不可分的核心概念,它们共同构成了人类面对宇宙真相时的终极困境。这一关系不仅体现在洛夫克拉夫特的文本中,更深刻地反映了存在主义哲学、认知局限与人类心理的冲突。以下从多个维度展开分析: 一、不可知的本体论意义1. 宇宙的终极荒诞 克苏鲁神话中的“不可.............
  • 回答
    好的,我们来详细地、深入地理解利率平价理论(Uncovered Interest Parity,UIP)中利率和汇率的关系。核心思想:无抛补利率平价理论(UIP)在探讨利率和汇率的关系之前,我们必须明确 UIP 的核心思想是什么。简单来说,UIP 认为:在不存在交易成本和套利机会的情况下,持有不同货.............
  • 回答
    好的,我们来深入聊聊《Effective C++》第31条,关于如何降低文件间的编译依赖关系这个至关重要的话题。这不仅是为了提高编译速度,更是为了构建更易于维护、更灵活的 C++ 系统。想象一下我们正在开发一个大型 C++ 项目。随着功能的不断增加,我们不可避免地会创建越来越多的头文件(.h/.hp.............
  • 回答
    咱们聊聊数学里一个挺有意思的规则,就是“若 A 不真,则 A→B 总是真的”。这听起来有点绕,但其实它背后有着非常朴素的逻辑,咱们一层一层来剥开。首先,得明白我们说的是什么。在数学里,我们经常会遇到一些命题,这些命题要么是真的,要么是假的,没有中间状态。比如,“2加2等于4”就是一个真命题,“太阳从.............
  • 回答
    咱们今天就来聊聊一个有点“硬核”但特别有意思的话题:利息到底是怎么来的?尤其是在咱们马克思主义的语境下,利息可不是那么简单的“钱生钱”那么简单,它背后藏着劳动和剥削的故事。要理解“利息的本质是职能资本家和借贷资本家共同剥削产业工人剩余劳动的关系”,咱们得先拆开来看,弄明白几个关键人物和他们的“活计”.............
  • 回答
    想象一下,我们人类需要沟通,但我们使用的语言(比如中文、英文)千差万别。计算机的世界里也是如此,不同层级的“语言”之间也存在着微妙而清晰的对应关系。理解这一点,就像是掌握了一套解码器,能够窥探程序运行的深层机制。我们常说的“上层语言”,比如我们熟悉的Python、Java、C,它们就像是人类使用的自.............
  • 回答
    美防长亲封立陶宛为“民主灯塔”:口头承诺的解读与美立关系发展展望美国国防部长劳埃德·奥斯汀近期访问立陶宛,并将其称为“民主灯塔”,同时承诺美国将与之共同应对挑战。这一表态在国际政治舞台上引起了广泛关注,尤其是在当前复杂的地缘政治背景下。理解这一口头承诺的深层含义,并展望美立关系未来的发展,需要从多个.............
  • 回答
    最近有消息说,每天多吃一个鸡蛋,糖尿病风险会增加60%,这听起来确实挺让人担心的,毕竟鸡蛋是我们日常饮食中非常常见的一种食物。但咱们得冷静下来,好好看看这背后到底是怎么回事,以及鸡蛋跟我们身体健康究竟是什么关系。首先,关于“每天多吃1个鸡蛋,糖尿病风险增加60%”这个说法,咱们需要知道,科学研究往往.............
  • 回答
    玉石的细腻、晶粒大小与透明度之间的关系,确实是鉴赏和理解玉石品质的关键点。用行内人的话说,“质地越细腻,晶粒越小,透明度越高”,这句话看似简单,背后却蕴含着丰富的物理和地质学原理。我们就来好好说道说道。 晶粒与透明度的关系:为何晶粒越小,玉石越透?简单来说,晶粒越小,玉石的透明度往往越高。这就像我们.............
  • 回答
    想象一下,你站在一座连绵起伏的山丘上。这座山丘的高度,可以看作是我们今天要讲的“函数”的值。它在你脚下,是你所在位置的“海拔”。方向导数:顺着某个特定方向“爬坡”或“下山”的速度现在,你想从你当前所在的位置,往某个特定方向走。这个方向可以是正东,可以是东北,也可以是任何一个你指向的角度。 方向导.............
  • 回答
    五胡十六国:混乱与融合中的历史脉络五胡十六国时期(公元304年—439年)是中国历史上一个极其混乱而又充满活力的时期。北方汉族政权崩溃,多个少数民族政权在中国北方和中原地区崛起,彼此征伐、分分合合,既有激烈的冲突,也有文化的融合,最终为南北朝的形成奠定了基础。要理清五胡十六国的脉络关系,我们需要从以.............
  • 回答
    好的,我们将深入探讨中国古代王朝与中亚、西亚国家的关系,以及如何将中国历史与欧洲历史联系起来。这是一个庞大而迷人的话题,涉及文化交流、经济贸易、政治联盟、军事冲突等多个层面。 中国古代王朝与中亚和西亚国家的关系中国古代王朝与中亚和西亚国家的关系,可以理解为一条贯穿千年、复杂而动态的互动链条。这条链条.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有