@artman328
2019-11-05T02:15:20.000000Z
字数 4638
阅读 2370
db
规范化
就是让数据库管理系统中表的设计满足一定要求——不要让同一信息数据在多个地方重复存储,避免出现数据不一致;不要让关联的表发生参照失败,避免出现数据的不完整,同时还要顾及数据的处理效率。
为了做到这一点,数据库表应当至少做到第三级规范标准,即第三范式。前两级分别是第一、第二范式。
将表的设计从满足第一级规范开始逐级满足到一定范式(通常第三范式就足够了),就是数据表的规范化。
第一范式要求:
1. 不要有完全相同的行;
2. 每个单元格只能有唯一值;
3. 每个单元格的值应当具有原子性(足够细致,不可再细化);
4. 每一列的所有单元格里的数据类型一致;
5. 列的顺序无关紧要。
不要着急,我们慢慢解读。
为什么?废话,不解释。
如何解决?找出表格中确定具有唯一值(不可能在多行出现重复值)的一列。如果没有这样的一列,那就要找出组合起来可以确定具有唯一性的几列。如果再找不出来,说明设计有缺陷,需要去找出这样的列或几列加入到表的结构中。
“每个单元格只能有唯一值”,就是说,不要想在一个地方(单元格)放太多东西(多值),计算机并没有聪明到能够很好地处理杂乱无章的东西。比如,某公司需要记录员工信息如下(需求:记录员工所有电话,但员工必须有且只记录一个属于自己的电邮):
姓名 | 性别 | 出生年月 | 联系电话 | 电邮 |
---|---|---|---|---|
王小二 | 男 | 1998-01-29 | 129 0909 1223, 130 1010 1234, 0871-87876666 |
wanggang@theserver.com |
现在联系电话一栏就有多个电话,计算机要处理查找电话就非常不便(花费太多的时间或者因数据不规范而变得不可能)。
如何解决?
不要试图在列上解决多值问题(如下表),因为到底可能有多少值,事先是不知道的。因为有更多值,就去改变表的结构?所有用你数据库的人都会说:“你杀了我吧!” —— 他们如果要获得所有电话,程序就要改写!
姓名 | 性别 | 出生年月 | 联系电话1 | 联系电话2 | 联系电话3 | 电邮 |
---|---|---|---|---|---|---|
王刚 | 男 | 1998-01-29 | 129 0909 1223 | 130 1010 1234 | 0871-87876666 | wanggang@theserver.com |
不要试图把多值变多行(如下表),那会带来太多的重复数据。重复数据的危害是:同一数据到处存,可能造成数据不一致!比如我的姓名有的地方是王小二,有的地方可能是王二小,以谁为准?这就是数据不一致导致的问题,而这正是数据表规范化所要避免的。
姓名 | 性别 | 出生年月 | 联系电话 | 电邮 |
---|---|---|---|---|
王小二 | 男 | 1998-01-29 | 129 0909 1223 | wanggang@theserver.com |
王小二 | 男 | 1998-01-29 | 130 1010 1234 | wanggang@theserver.com |
王二小 | 男 | 1998-01-29 | 0871-87876666 | wanggang@theserver.com |
拆分表格——把多值取出来,放到一个新表里。我们先来试试:
员工信息表
姓名 | 性别 | 出生年月 | 电邮 |
---|---|---|---|
王小二 | 男 | 1998-01-29 | wanggang@theserver.com |
电话表
电话 | 属于 |
---|---|
129 0909 1223 | ? |
130 1010 1234 | ? |
0871-87876666 | ? |
拆分表格需要建立相互之间的联系,否则数据就出现了不完整性(记住,重复数据会导致数据不一致,表格失去关联就会导致数据不完整!比如:这电话是谁的?)。我们来考虑电话属于谁怎么建立——填姓名?可能会有另外一个王小二(同名同姓)。填性别/出生年月?你也知道这简直是搞笑吧?看业务需求说,每个员工“必须有且只记录一个属于自己的电邮”,所以,这里填电邮是绝对没问题的。
电话 | 属主电邮 |
---|---|
129 0909 1223 | wanggang@theserver.com |
130 1010 1234 | wanggang@theserver.com |
0871-87876666 | wanggang@theserver.com |
在这里,员工表信息里的“电邮”具有唯一性,从而能够保证没有任意两行记录完全相同。这一列就叫“主键列”,而电话表里的“属主电邮”指出了电话号码属于谁,参照的是员工信息表的主键,它叫做“外键”。
主键的改进。主键的选择原则:(1)主键应当具有唯一值;(2)主键应当极少需要改变(否则参照关系就需要更新,这可能是一项繁重的任务,当参照它的外键很多的时候)。在员工信息表中,“电邮”虽然具有唯一性,但却具有被改动的可能性。解决办法:添加一列流水号列(流水号没有被改动的需要),作为主键,电邮成为“候选键”(可做主键但未做),然后在建表建立约束时,对它建立唯一约束。而对于“电话表”来说,一个属主可能有多个电话,而一个电话可能有多个属主(比如在同一个办公室的员工,具有相同的办公电话),但“电话”和“属主电邮”组合必须唯一(别忘了第1条:“不要有完全相同的行”),因此二者组合就是主键。但通常为了处理上的高效,我们会用一个流水号列做主键,而把它们的组合约束为唯一。
应当注意的是,用流水号做主键,目的是让数据库系统处理参照时尽可能高效。由于流水号并不具有事务上的意义,所以要保证至少还要有候选键列的存在,否则记录仍然会有不确定性。如:
编号(流水) | 姓名 | 性别 | 出生年月 |
---|---|---|---|
1 | 李丹 | 女 | 1980-12-30 |
2 | 李丹 | 女 | 1980-12-30 |
此表因流水号的存在,符合第一范式要求,但这两列记录是否重复?有可能,但因流水号的存在掩盖了数据的重复。
但是:
编号(流水) | 姓名 | 性别 | 出生年月 | 身份证号 |
---|---|---|---|---|
1 | 李丹 | 女 | 1980-12-30 | 530103198012300026 |
2 | 李丹 | 女 | 1980-12-30 | 530552198012300022 |
有了一列候选键列“身份证号”,只要“身份证号”不同,就知道这其实是两个不同的人。事实上,因为给“身份证号”加了唯一约束,“身份证号”就被保证不会重复了。
回到我们的员工表和电话表,主键改进后:
员工信息表(候选键:电邮)
员工编号 | 姓名 | 性别 | 出生年月 | 电邮 |
---|---|---|---|---|
1 | 王小二 | 男 | 1998-01-29 | wanggang@theserver.com |
电话表(候选键:电话+员工编号)
编号(流水) | 电话 | 员工编号 |
---|---|---|
1 | 129 0909 1223 | 1 |
2 | 130 1010 1234 | 1 |
3 | 0871-87876666 | 1 |
这回就以流水号作为主键,主外键关系就能保证很稳固了。
是否具有原子性,要看具体的事务需求。如果有需要处理姓氏的需求,则下表的“姓名”一列就不够原子性。
员工信息表(候选键:电邮)
员工编号 | 姓名 | 性别 | 出生年月 | 电邮 |
---|---|---|---|---|
1 | 王小二 | 男 | 1998-01-29 | wanggang@theserver.com |
需要做如下改进:
员工信息表(候选键:电邮)
员工编号 | 姓 | 名 | 性别 | 出生年月 | 电邮 |
---|---|---|---|---|---|
1 | 王 | 小二 | 男 | 1998-01-29 | wanggang@theserver.com |
2 | 欧 | 阳明 | 男 | 1997-10-01 | ouyangming@theserver.com |
3 | 欧阳 | 明 | 男 | 1995-02-16 | ouyangming216@theserver.com |
这也是显而易见的,不用多说。
就是说,列的顺序并不影响对记录的描述。也就是说,表的列之间,并不存在某种排列关系。它们只是描述一个事物的各个属性的组合,不会因为属性排列的先后就导致所描述的对象有所不同。难懂吧?其实你想要违反还真不容易!不懂就先搁着,不会影响后续学习。
第二范式就两条:
1. 要满足第一范式;
2. 不要有部分依赖。
我们仍然用通俗的话来讲。
先看着张表:
员工工作量统计
员工编号 | 员工姓名 | 员工性别 | 项目编号 | 项目名称 | 项目地址 | 工时数 |
---|---|---|---|---|---|---|
1 | 李明 | 男 | 3 | 昆明正通大厦装修设计 | 昆明市正通路23号 | 220 |
2 | 张灵 | 女 | 3 | 昆明正通大厦装修设计 | 昆明市正通路23号 | 100 |
1 | 李明 | 男 | 5 | 安宁镜湖小区绿化规划 | 安宁市明日路99号 | 120 |
我们看到了重复数据,怎么来的?
这是一张员工工作量统计表,与员工和项目相关。但我们看到“员工编号”和“项目编号”都有重复值,不可能单独做主键。但二者的组合必须唯一,否则就会出现相同的行或错误的行。因此二者组合为唯一组合候选键列(可做主键的列)。
所谓部分依赖,是指表中有的列只依赖于组合候选键中的部分列(或者说只决定于组合候选键中的部分列)。
我们考察这张表,“员工姓名”和“员工性别”,只决定于"员工编号”,与“项目编号”无关;“项目名称”和“项目地址”只决定于“项目编号”,于“员工编号”无关。这些都是“部分依赖”。
出现部分依赖,是因为试图在涉及多个关联事物的一张表中描述只属于单个事物的属性,从而出现了大量不合理的重复数据。为了不丢失这些属性,采用拆分表的办法解决重复数据问题。
员工工作量统计
员工编号 | 项目编号 | 工时数 |
---|---|---|
1 | 3 | 220 |
2 | 3 | 100 |
1 | 5 | 120 |
员工信息
员工编号 | 姓名 | 性别 |
---|---|---|
1 | 李明 | 男 |
2 | 张灵 | 女 |
项目信息
项目编号 | 名称 | 地址 |
---|---|---|
3 | 昆明正通大厦装修设计 | 昆明市正通路23号 |
5 | 安宁镜湖小区绿化规划 | 安宁市明日路99号 |
把只依赖部分主键的列抽取,放到另一张表,两表建立参照关系。
不,大部分问题先前已经解决了。
我们来看看怎么说。
表中有的列并不直接依赖于主键,而是依赖其它直接依赖于主键的列。
我们用实例看一下。
产品编号 | 产品名称 | 型号规格 | 厂商 | 厂商电话 | 厂商地址 |
---|---|---|---|---|---|
P0001 | 打印机 | Brother 23 | 浙江天地科技公司 | 0570-98767898 | 浙江台洲某大道133号 |
P0002 | 显示器 | LG 2876 | 天津LG分厂 | 022-76767890 | 天津市滨江大道77号 |
P0003 | 路由器 | 7685 | 浙江天地科技公司 | 0570-98767898 | 浙江台洲某大道133号 |
在这张表中,“产品编号”是主键。“厂商”直接与“产品编号”有关(浙江天地科技公司生产的 Brother 23 打印机,编号为 P0001 ,而不是电话号码是 0570--98767898 的拥有者生产的 Brother 23 打印机,编号为 P0001 ,因为电话号码可能被改变,地址也是如此),但“厂商电话”、“厂商地址”却只与“厂商”有关。这就是传递依赖。
另一个实例。
员工编号 | 发放年月 | 应发 | 应扣 | 实发 |
---|---|---|---|---|
E001 | 2018-05 | 7800 | 600 | 7200 |
“实发”依赖于“应发”和“应扣”,而“应发”和“应扣”依赖于“员工编号”和“发放年月”(组合主键),这也是一种传递依赖。事实上,“实发”是一个可由其它计算出来的列。
第一种情况拆分表格。
产品编号 | 产品名称 | 型号规格 | 厂商编号 |
---|---|---|---|
P0001 | 打印机 | Brother 23 | M001 |
P0002 | 显示器 | LG 2876 | M002 |
P0003 | 路由器 | 7685 | M001 |
厂商编号 | 厂商 | 厂商电话 | 厂商地址 |
---|---|---|---|
M001 | 浙江天地科技公司 | 0570-98767898 | 浙江台洲某大道133号 |
M002 | 天津LG分厂 | 022-76767890 | 天津市滨江大道77号 |
第二种情况,直接删除计算列。
员工编号 | 发放年月 | 应发 | 应扣 |
---|---|---|---|
E001 | 2018-05 | 7800 | 600 |