数据库(设计与应用)

关系数据库理论

数据依赖

数据依赖分为函数依赖(Functional Dependency,FD)多值依赖(Multi-Valued Dependency,MVD)

规范化

函数依赖

设一个数为x,通过一定的规则f,唯一地得到一个数y,记为y=f(x),则称y对x有函数依赖。(在数学上有多值函数,即一个x对应多个y,但在计算机中函数依赖只对应一个y)

在上面的例子中,当一个学生的学号确定下来时,这个学生所处的系也就确定下来,即 S d e p t = f ( S n o ) Sdept=f(Sno) Sdept=f(Sno),那么系主任的名字也随即确定了下来,即 M n a m e = f ( S n o ) Mname=f(Sno) Mname=f(Sno)。但是当学生的学号确定下来时,他的成绩要和课程号组合才能确定,所以 G r a d e = f ( S n o , C n o ) Grade=f(Sno,Cno) Grade=f(Sno,Cno)

函数依赖还可以这么表示x->y以为x函数确定y。

函数依赖分为几种形式:

  1. x → y x→y xy,但 y ⊈ x y\nsubseteq x yx,则 x → y x→y xy是非平凡函数依赖。若无特别说明,所有的函数依赖均指此。
  2. x → y x\to y xy,且 y ⊆ x y\subseteq x yx,则此为平凡的函数依赖。
  3. x x x称为决定因素(determinant)
  4. 如果x决定y,且x的任何一个真子集都不能决定y,则称y对x完全函数依赖。比如说上面例子中的 S n o → S d e p t Sno\to Sdept SnoSdept
  5. 除4以外,称为部分函数依赖,比如 ( 安 徽 , 上 海 ) → 中 国 (安徽,上海)\to 中国 (),但是仅仅只有安徽便可以决定中国,所以左边的条件对于右边来说是多余的。

如果关系中的其他属性完全依赖于一个或一组属性K,则称K为关系的码。比如SC(Sno,cno,Grade),Sno和Cno的组合决定了Grade,所以称(Sno,Cno)为关系SC的码。

至于全码和外码的概念,在基本章已经有所阐述。

第一范式(1NF)

范式,就是规则的意思,意为设计数据库是要遵循的规范。

一个数据库,最基本的规定是一个分量必须是不可分的数据项,这也就是数据库的第一范式(1NF)。我们设计一个学生管理系统,属性有学号(Sno)、系主任姓名(Mname)、系名(Sdept)三个属性,课程有课程号(Cno)、成绩(Grade)两个属性。根据1NF,可以把表设计成如下形式。

SnoSdeptMnameCnoGrade
S1计算机系张明C195
S2计算机系张明C190
···············

但是,很明显,这个表是有问题的。

  • 数据冗余

    比如说我们要插入一个新的学生的信息,就要重复地插入系名、系主任名和课程名,这大大浪费了存储空间。

  • 更新异常(update anomalies)

    比如计算机系更换了系主任,那么就要把所有元组的Mname属性全部修改一遍。

  • 插入异常

    假如一个系刚成立,还没有学生,那么该系的系主任信息就无法存入数据库。

  • 删除异常

    假如学生全部毕业,那么系主任的信息也一并丢失。

通过上面的分析,我们可以看到,把所有属性一股脑地塞进一个表中是会出现很多问题的。那么我们便会很容易地想到,把学生的信息和系主任的信息分开保存。至于如何分开,便是下面要讨论的问题。

img

第二范式(2NF)

2NF规定:全部非主属性都要完全依赖于码。

我们假设有个关系货物(货物类型,货物ID,货物名称,注意事项),其中货物名称为完全依赖于主键,但是注意事项仅仅只依赖于货物类型这一个属性。这样会产生一些问题。

  • 插入异常

    假如说该公司要新增加一种新货(比如CPU芯片),但是目前没货,那么货物关系中就只有货物类型,而没有货物实体(CPU芯片并没有开始生产),也就没有货物ID,但是关键码必须给定确定的值,所以产生异常。

  • 删除异常

    假如说该公司只有一个CPU芯片,若这唯一的一个CPU芯片出售了,那么整个元组就都要删除,CPU芯片这个货物的信息也一并被删除,那么CPU芯片的注意事项就在数据库中删除了,如果客户寻求售后服务,就会出现问题。

  • 修改复杂

    当CPU的注意事项属性需要修改时,就要把表中的所有的货物类型为CPU芯片的元组全都修改。

如果仔细观察,上面问题产生的原因就是因为注意事项仅仅和货物类型有关系,但是和货物ID无关。为了解决这个问题,我们可以把这一个表拆成库存货物(货物类型,货物ID,货物名称)货物(货物类型,注意事项)

img

第三范式(3NF)

但是2NF并没有完全消除上面的问题。

假设一个关系学生管理(学号,所在系,公寓号),规定学号 → \to 所在系,所在 → \to 公寓号学号 → \to 公寓号。由于这个表的码只有一个元素,所以一定满足2NF。

  • 插入异常

    假设学校建了一个新公寓,还没有学生入住,那么这个新公寓的信息就无法存储。

  • 删除异常

    假设一个系的学生全部毕业,那么公寓信息也一并删除。

  • 修改异常

    若要修改所在系,那么公寓号也要一并删除。

  • 数据冗余

    同一个系的学生就会有相同的公寓号,造成大量存储空间的浪费。

上面的问题主要是因为学号 → \to 所在系,所在系 → \to 公寓号,所以可以直接推出学号 → \to 公寓号,那么学号 → \to 所在系,所在系 → \to 公寓号这两个函数依赖关系就没有必要在一个表里,所以可以把它拆成两个表学-系(学号,所在系)系-公寓(所在系,公寓号)

img

3NF规定:在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)

BCNF

3NF也会产生一些问题,请看下面的例子。

在这里插入图片描述

  1. 如果此时在表中增加一个新书名,但是没有插入作者名字,此时主属性作者为空,根据数据表的完整性要求,主键不为空,所以此时存在插入异常。
  2. 如果书名换了作者,可能会同时修改数据表中的多个记录。
  3. 如果删除某书,此时书号和书的作者也会被删除。

BCNF规定:在3NF基础上,每一个决定因素都包含码

由于BCNF的本质其实就是消除3NF中比较隐含的函数传递关系,所以也叫扩充的3NF(不叫4NF)。

多值依赖

我们先看一种情况。假设一个仓库W只有一个保管员S,那么就有 W → S W\to S WS,这种一对一的函数依赖上面已经说过。但是大多数实际情况是一个W会有多个S来保管,这就构成了多值依赖,表示为 W → → S W\to \to S WS。多值依赖有以下几种类型。

  • 平凡的多值依赖

    设总集U,X、Y、Z均为U的子集, X → → Y X\to \to Y XY,且 Z = ϕ Z=\phi Z=ϕ,则X和Y为平凡的多值依赖。如上面这一种情况。

  • 非平凡的多值依赖

    如果在上面的例子中加入一个货物C属性,并规定一个W可以保存多个C,有多个S看守,那么W和S就变成了非平凡的多值依赖。

第四范式(4NF)

我们参照上面WCS关系的例子,想象仅有一个仓库,有n个保管员保管,里面存有m个货物,那么这个表的元组就有 n × m n\times m n×m个,同时仓库W大量冗余存储。

那么我们可以吧WCS关系拆分成WS和WC,这样虽然WS和WC依然是多值依赖,但已经变成了平凡的多值依赖,假设依然只有一个仓库W,前一个表的元组有n个,后一个有m个,与上面相比存储空间大大缩小。

4NF规定:限制关系模式的属性之间不允许有非平凡且非函数依赖的多值依赖

4NF的关键在于将非平凡的多值依赖中转化成平凡的多值依赖。

规范化总结

  • 1NF:每个分量必须是不可分的数据量
  • 2NF:全部非主属性都要完全依赖于码
  • 3NF:在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)
  • BCNF:在3NF基础上,每一个决定因素都包含码
  • 4NF:限制关系模式的属性之间不允许有非平凡且非函数依赖的多值依赖

求闭包

若属性集 X F + = A ∣ X → A 能 被 A r m s t r o n g 公 理 系 统 导 出 X_F^+={A|X\to A能被Armstrong公理系统导出} XF+=AXAArmstrong,则 X F + X_F^+ XF+为X关于函数依赖F的闭包。

**例:**关系模式R(U,F),其中U={A,B,C,D,E,I},F={A→D,AB→E,BI→E,CD→I,E→C},计算(AE)+。

解:

第一步:令X={AE},X(0)=AE。

第二步:求X(1)。逐一扫描F集合中各个函数依赖,在F中找出左边是AE子集的函数依赖,其结果是:A→D,E→C。这是,F中这两个函数依赖要打上标记(我通常是打上√,表示已经用过,后面不能用了)。于是X(1)=AE∪DC=ACDE;

第三步:判断X(1)是否等于 X(0)以及 X(1)是否等于 U。

在这里,X(1)≠ X(0),且X(1)≠ U,所以在F中继续找出左边是ACDE子集的函数依赖,其结果是:CD→I。同样打上标记。于是X(2)=ACDE∪I=ACDEI。(这里有一个注意点,∪右边的元素只写左边没有的)

继续判断,虽然X(2)≠ X(1),但在F中未用过的函数依赖的左边属性已没有X(2)的子集,所以不必再计算下去,即(AE)+=ACDEI。

这里总结一下解题套路

**第一步:**X(0)=X。

**第二步:**求X(1)。就是在F中找,左边是X(0)的元素子集的函数依赖,打上标记√。

**第三步:**判断X(1)是否等于 X(0)以及 X(1)是否等于 U。

这里有四种情况

**第一种:**X(i)= X(i-1),说明已经找到闭包。

**第二种:**X(i)≠ X(i-1)但是X(i)= U,说明已经找到闭包。

第三种:都不相等,但是在F中未用过的函数依赖的左边属性已没有X(i)的子集,所以不必再计算下去,已经找到闭包。

第四种:都不相等,在F中未用过的函数依赖的左边属性还有X(i)的子集,重复执行。

求最小依赖集

**例:**U=(A,B,C,D,E,G) F={BG->C,BD->E,DG->C,ADG->BC,AG->B,B->D},求F最小依赖集。

解:

第一步:右边单一化。

F1={BG->C,BD->E,DG->C,ADG->B,ADG->C,AG->B,B->D}

第二步:逐个求,在去掉它的F中求闭包,如果包含右边属性,则表示这个函数依赖要去掉。

BG->C:求(BG)+=BCDEG,包含右边属性C,所以去掉。

BD->E:(BD)+=BD,不包含右边E,所以不用去掉。

DG->C:(DG)+=DG,也不用去掉。

ADG->B:(ADG)+=U,包含B,所以去掉。

ADG->C:(ADG)+包含C,去掉。(在这里,求闭包的时候,不能再用前面去掉的函数依赖了,所以如果从后往前判断,可能不用去掉ADG->B,所以最小依赖集不唯一,写出一个即可。)

AG->B:(AG)+=AG,不用去掉。

B->D:(B)+=B,不用去掉。

所以F2={BD->E,DG->C,AG->B,B->D}

第三步:对左边属性单一化,判断冗余,代替。

BD->E:B->E,求(B)+=BD,包含D,所以D冗余。

​ D->E,求(D)+=D,所以B不冗余。

​ 所以用B->E代替BD->E。

DG->C:D->C,(D)+=D,所以G不冗余。

​ G->C,(G)+=G,所以D不冗余。

AG->B:A->B,(A)+=A,所以G不冗余。

​ G->B,(G)+=G,所以A不冗余。

所以Fm={B->E,DG->C,AG->B,B->D}。

数据库设计

在数据库设计中,有“三分技术,七分管理,十二分基础数据”的说法,所以设计一个数据库,大部分都不是关于技术的,本文主要探讨关于技术的部分。

数据库设计有以下6个阶段

  • 需求分析(甲方事真多阶段)
  • 概念结构设计
  • 逻辑结构设计
  • 物理结构设计
  • 数据库实施
  • 数据库运行和维护

概念结构设计(E-R图)

E-R图是用来表示实体之间的联系。这一章没有题目很难理解,最好看书。

两个实体之间的联系

联系和实体之间存在三种关系:

  1. 一对一。比如:一个学校只能有一个校长,而一个校长也只能担任一个学校的校长。

这里写图片描述

  1. 一对多。比如:一个学校里有多名教师,而每个教师只能在一个学校教学。

这里写图片描述

  1. 多对多。比如:一个学生可以上n种课程,而每一个课程可以有m个学生学习。而上课还可以有一个属性:上课时间。

这里写图片描述

多个实体之间的联系

  1. 多个实体之间一对多。比如:一门课程可以有n个老师来授课,一个老师只讲一门课程,该门课程可以有m本参考书,而这m本书只供这一门课使用。

这里写图片描述

  1. 多个实体之间多对多。比如:一个供应商可以给多个(n)项目提供零件,每个项目可以使用多个(m)供应商供应的零件,每种§零件可有不同供应商供给。

这里写图片描述

将上面的关系组合在一起,就构成了一个完整的E-R图,多个E-R图也可以相互组合。而在组合的过程中可能会有冲突产生。最主要的是结构冲突,比如对象的属性不一样,有些对象在不同的应用中有不同的抽象,比如职工在一个应用中被当做实体,在另一个应用中被作为对象。

逻辑结构设计

E-R图转关系模型

这一章还是要看书,没有例子很难说清楚。

  • 1对1:相互吸收对方的主码作为外码,若联系有属性,则将联系作为独立的关系

  • 1对n:n端吸收1端的主码作为外码,若联系有属性,则将联系作为独立的关系

  • n对n:关系两端和关系均作为独立关系。

  • 三个或三个以上的实体的多元联系,将实体和联系均作为独立关系,其中联系关系的主键由实体的主键组成

    比如上面的例子可以转换成

    供应商(供应商名,…)

    项目(项目名,…)

    零件(零件名,…)

    供给(供应商名,项目名,零件名…)

数据库编程

随着数据库的发展,SQL语句也渐渐地向高级语言的方向融合和进化。

嵌入式SQL

嵌入式SQL就是把SQL语句和其他高级语言混合编程,SQL语句用来控制数据,其他高级语言用来控制逻辑流程。语法格式为

EXEC SQL<SQL语句>;

我感觉这一章不重要,因为现在有更好的高级语言和数据库连接交互的方案(JDBC,各种框架等等),所以只讲重要的概念。

  1. 编写主变量,使用EXEC SQL BEGIN DECLARE SECTION开始,使用``EXEC SQL BEGIN DECLARE SECTION结束。需要注意的是在SQL语句中主变量前面需要加冒号:`以示区分。

  2. 定义SQL通信区,使用EXEC SQL INCLUDE SQLCODE。通信区中有一个变量SQLCODE,这个变量被赋予每次执行SQL语句后返回的代码,程序员用来根据代码查找错误。该变量要在定义通信区之前设定。

  3. 建立和关闭数据库连接,使用EXEC SQL CONNECT TO XXX@DB,用来连接本地或云端上的数据库。使用EXEC SQL DISCONNECT XXX关闭连接。

  4. 定义游标(cursor)。游标是一个数据缓存区,因为大多数高级语言的变量只能存储一个数据,而SQL语句返回的常常是个集合,所以使用游标。使用

    EXEC SQL DECLARE SX CORSOR FOR
    SX对应的语句
    

    来设置游标。使用EXEC SQL OPEN SX;打开游标。使用EXEC SQL FECTH SX INTO :XXX,:AAA;推进游标。使用EXEC SQL CLOSE SX;关闭游标。

  5. 在更改结束之后,还要EXEC SQL COMMIT WORK;提交更新。

过程化SQL

过程化SQL允许用户在SQL语句中设置变量和常量,写循环语句和条件控制语句。很少用。略。

存储过程

  • 定义

是一组预编译的sql语句,也就是给pl/sql语句包装起来,完成一次创建任意调用的功能,相当于java中的方法。经编译之后存储在数据库中,用户通过制定存储过程的名字并给出参数(如果该存储过程带有参数的话)来执行它。

  • 语法
create [or replace] procedure 存储过程名(参数名1 in/out 参数类型1,参数名2 in/out 参数类型2is/as

——声明部分

begin

——业务逻辑部分

end;

举个栗子:给指定员工涨薪,并打印涨薪前和涨薪后的工资。

    create or replace procedure proc_updatesal(vempno in number,vnum in number)
    is
      --声明变量.记录当前工资
      vsal number;
    begin
      --查询当前的工资
      select sal into vsal from emp where empno = vempno;
      --输出涨薪前的工资
      dbms_output.put_line('涨薪前:'||vsal);
      --更新工资
      update emp set sal = vsal + vnum where empno = vempno;
      --输出涨薪后的工资
      dbms_output.put_line('涨薪后:'||(vsal+vnum));
      --提交
      commit;
    end;

存储函数

  • 定义

通常存储函数是给存储过程调用的。

  • 语法
create [or replace] function存储函数名(参数名1 in/out 参数类型1,参数名2 in/out 参数类型2) return 返回类型

is/as

——声明部分(声明结果变量)

begin

——业务逻辑部分

return (结果变量);

end;

举个栗子:查询指定员工的年薪。

    create or replace function func_getsal(vempno number) return number
    is
      --声明变量.保存年薪
      vtotalsal number;
    begin
      select sal*12 + nvl(comm,0) into vtotalsal from emp where empno = vempno;
     return vtotalsal;
    end;

存储过程和存储函数的区别

  1. 存储函数可以有返回值也可以没有返回值,存储函数不许有返回值

  2. 存储过程和存储函数都可以通过输出参数out实现多个返回值

怎么选择?

原则上只有一个返回值用存储函数,否则用存储过程

但是我们一般都是用存储过程,因为

  1. 存储过程可以有返回值也可以没有返回值,存储的灵活性

  2. 存储过程既然有返回值了,可以替代存储函数

  3. oracle新版中已经不推荐使用存储函数了


参考文献

https://blog.csdn.net/weixin_43971764/article/details/88677688

https://baike.baidu.com/item/第二范式/3193760?fr=aladdin

https://blog.csdn.net/fjxcsdn/article/details/105310435

https://wonzwang.blog.csdn.net/article/details/80464466

https://blog.csdn.net/chenpidaxia/article/details/62073162

https://www.cnblogs.com/sun7897/p/8592931.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值