数据库期中复习笔记
《数据库》期中复习
只有两个重点:查询表的方法和设计表的模式。
第二章 关系数据模型
数据模型是用于描述数据或信息的标记,由数据结构、数据操作和数据上的约束条件组成。
一个关系的列被称为属性 attribute,关系名和其属性的集合称为关系的模式 schema,关系的每一行称为元组 tuple,元组的每个分量所属的类型称为域 domain,一个给定关系中的一个元组的集合叫做关系的一个实例 instance。
键 key是关系的一组属性组成的集合,通过键的定义,可以保证一个关系实例中在这组属性上的元组是唯一的。
(a) 考虑三个元组的全排列为 \(3!\) 三个属性的全排列为 \(3!\) 答案为 \(3!*3!=36\)
(b) 略
(c) 容易证明答案 \(n!m!\) 如果考虑元组出现重复情况,每种元组的个数为 \(a_1, a_2, ..., a_k\) 答案应该是 \(\frac{n!m!}{a_1!a_2!...a_k!}\)
SQL 支持的数据类型包括:
-
可变长度字符串
VARCHAR(n)
,表示最多可有n
个字符的字符串;固定长度字符串CHAR(n)
,表示恒定有n
个字符的字符串。 -
位串
BIT(n)
,表示恒定有n
个比特的串;可变位串BIT VARYING(n)
,表示最多有n
个比特的串。 -
逻辑类型
BOOLEAN
,可取值为TRUE
、FALSE
或UNKNOWN
。 -
整数类型
INT
,SHORTINT
。 -
浮点类型
FLOAT
,DOUBLE
,表示浮点数;高精度的浮点类型DOUBLE PRECISION
;指定小数点后位数的DECIMAL(n, d)
,表示有n
位有效数字,小数点在右数第d
位。 -
日期和时间类型
DATE
,TIME
;为了区分日期和字符串类型,往往需要额外添加DATE
关键字,比如DATE '2020-10-19'
表示一个日期。
简单的表定义如下:
CREATE TABLE <relation name>
(
<attribute name> <data type>,
...
<attribute name> <data type>
);
删除某个关系:
DROP TABLE <relation name>;
修改关系中的某个属性(增加新的属性或者删除原有属性):
ALTER TABLE <relation name>
ADD <attribute name> <data type>;
ALTER TABLE <relation name>
DROP COLUMN <attribute name>; -- MySQL 中使用 DROP COLUMN
为了避免用 NULL
填充默认值,可以在创建表或者增加新的属性的时候指定默认值:
CREATE TABLE <relation name>
(
<attribute name> <data type> DEFAULT <default value>,
...
<attribute name> <data type> DEFAULT <default value>
);
ALTER TABLE <relation name>
ADD <attribute name> <data type> DEFAULT <default value>;
有两种方式为关系指定键:PRIMARY KEY
和 UNIQUE
。PRIMARY KEY
指定的属性 S 不能有 NULL
值,但是 UNIQUE
可以允许 NULL
值。
CREATE TABLE <relation name>
(
<attribute name> <data type> PRIMARY KEY,
...
<attribute name> <data type> UNIQUE
);
(d) 答案如下:
CREATE TABLE Printer(
model VARCHAR(10) PRIMARY KEY,
color BOOLEAN,
type VARCHAR(10),
price DECIMAL(10, 2)
)
(e) 答案如下:
ALTER TABLE Printer
DROP COLUMN color;
(f) 答案如下:
ALTER TABLE Laptop
ADD od VARCHAR(10) DEFAULT 'none';
在关系上定义并、交、补运算:R 和 S 是具有同样属性集合的表,同时 R 和 S 的各个属性的域也必须匹配且有相同的顺序。
-
并运算:\(R \cup S\),包含 R 和 S 中的所有元组。
-
交运算:\(R \cap S\),包含 R 和 S 中共有的元组。
-
差运算:\(R - S\),包含 R 中但不在 S 中的元组。
投影操作用来从关系 R 生成一个新的关系,这个关系只包含 R 中的一部分属性。投影操作的结果是一个关系,其模式是 R 的属性的一个子集。
选择操作用来从关系 R 中选择出满足某个条件的元组。选择操作的结果是一个关系(元组的集合),其模式是 R 的模式。
笛卡尔积接受两个关系作为输入,生成一个新的关系,其模式是输入关系的元组的集合的笛卡尔积。
自然连接是一种特殊的连接操作,它是在两个关系的笛卡尔积上应用选择操作,选择那些在两个关系的公共属性上取值相等的元组。如果一个元组不能和另一个关系中的任何元组匹配,那么这个元组被称为悬浮元组 dangling tuple。
\(\theta\) 连接是一种连接操作,它是在两个关系的笛卡尔积上应用选择操作,选择那些满足某个条件的元组。当条件是在两个关系的公共属性上取值相等时,\(\theta\) 连接和自然连接是等价的。
有一个很有意思的等价关系可以连接笛卡尔积和自然连接:
定义条件 \(c\) 为 \(R.A = S.A \land R.B = S.B \land ...\),\(L\) 是在关系 \(R\) 和 \(S\) 中不在 \(R\) 和 \(S\) 的属性上的属性的集合,那么:
重命名操作是为关系的属性或者关系本身指定一个新的名字。重命名操作的结果是一个新的关系,其模式是原来的模式,但是属性名或者关系名被替换为新的名字。
(a)
\(\pi_{model}(\sigma_{speed >= 3.00}(PC))\)
(b)
\(\pi_{maker}(\sigma_{hd >= 100}(Product \bowtie Laptop))\)
(f)
\(\pi_{hd}(\sigma_{PC1.model <> PC2.model \text{ AND } PC1.hd = PC2.hd}(\rho_{PC1}(PC) \bowtie \rho_{PC2}(PC)))\)
已经讨论了数据结构和数据操作,接下来讨论数据约束。数据约束是关系数据库中的一种重要机制,用来保证数据的完整性和一致性。
用关系代数表示约束有两种风格:
-
“ R 的值必须为空”的约束,与“R中没有元组”等价,用 \(R = \emptyset\) 表示。
-
“R 的值必须是 S 的子集”的约束,用 \(R \subseteq S\) 表示。
这两种形式是等价的,因为 \(R \subseteq S\) 显然等价于 \(R - S = \emptyset\)。
引用完整性约束 referential integrity constraint 是一种常见的约束,如果关系 R 中的某个属性(A)的值 v,人们希望这个值也是关系 S 中的某个属性(B)的值,那么这个约束就是引用完整性约束。
键约束 key constraint 是一种约束,要求关系中的某个属性集合是一个键,这一约束同样可以用关系代数表示。
以上面的练习为例,可以有
此时,PC 的 model 构成了一个键。
通过上述的方式,我们就找到了一种彻彻底底形式化描述数据库的方法。
等价于
第三章 关系数据库设计理论
目前来说,一个关系模式可以设计的非常奔放,但是这显然不对,如果设计的太过冗余会导致物理资源的浪费,如果设计的太过简单会导致数据的不完整性。因此,我们需要在各个关系之间建立约束来描述它们之间的关系。
函数依赖 functional dependency 是一种约束,它描述了一个属性集合的值决定了另一个属性集合的值。如果一个属性集合 X 的值决定了另一个属性集合 Y 的值,即如果两个元组在 X 上值相同,在 Y 上也必定相同,那么我们称 X 函数依赖于 Y,记作 \(X \rightarrow Y\)。
我们在前面提到了键 key的概念,在这里我们会在函数依赖的语境下给出一个更加详细的定义:认为属性集 X 是关系 R 的一个键,当且仅当:
-
X 决定 R 的所有其他属性
-
X 的任意一个真子集不能决定 R 的所有其他属性
有时一个关系可能有多个键,我们指定其中一个为主键 primary key
一个包含键的属性集合称为超键 superkey
函数依赖 FD 可以由一系列规则推导:
-
传递规则:如果 \(X \rightarrow Y\) 且 \(Y \rightarrow Z\),那么 \(X \rightarrow Z\)
-
分解规则:如果 \(X \rightarrow YZ\),那么 \(X \rightarrow Y\) 和 \(X \rightarrow Z\)
-
结合规则:如果 \(X \rightarrow Y\) 和 \(X \rightarrow Z\),那么 \(X \rightarrow YZ\),是分解规则的对偶
-
平凡函数依赖:如果 Y 是 X 的子集,那么 \(X \rightarrow Y\);同时,也可以在右边删除那些在左边已经出现的属性,即如果 \(Z \subseteq X\),那么\(X \rightarrow YZ\) 等价于 \(X \rightarrow Y\)
-
增广规则:如果 \(X \rightarrow Y\),那么 \(XZ \rightarrow YZ\),需要消除两边相同的属性
假设 X 是属性集合,F 是函数依赖集合,X 的 闭包 \(X^+\) 使得每一个满足 F 的关系 R 也满足 \(X \rightarrow X^+\)。注意,这里讨论的闭包是“属性的闭包”,而不是关系的闭包。
用人话说,属性的闭包是一个属性集合,它保证函数依赖集合中的所有函数依赖都成立,或者形象地说,任何一个依赖都被限制在这个闭包中————就像加法之于正整数。另外,属性的闭包依赖于函数依赖集合,而不是关系实例。
属性的闭包算法:容易推导
满足下面三个条件的 FD 集合 B 被称为关系的最小化基本集 minimal basis:
-
B 中所有的 FD 都是不可分解的
-
从 B 中删除任何一个 FD 都会导致 B 不再是基本集
-
对于 B 中的所有 FD,如果从左边或者右边删除一个属性,那么这个 B 不再是基本集
函数依赖的投影是一个函数依赖集合的子集,它只包含某些属性的函数依赖。比如,令 \(X_1 = \pi(X)\),那么在 X 上成立的函数依赖只有部分在 \(X_1\) 上成立,因为有些属性在 \(X_1\) 上并没有定义。
函数依赖集的投影算法:
-
设 T 为最终的投影集,初始化为空
-
对于 \(R_1\) 中的每一个子集 X,计算 X 的闭包 \(X^+\);对于所有在闭包中也在 \(R_1\) 中的属性 Y,把所有非平凡的 FD \(X \rightarrow Y\) 添加到 T 中
-
构造 T 的最小化基本集
为什么算法中要计算闭包?因为通过闭包的计算可以自然建立 \(X \rightarrow Y\) 的关系,而且可以避免原 FD 集合并不是最大化(或许可以定义成函数依赖集的闭包)的情况。比如对于 \(R(A, B, C)\) 投影出 \(R_1(A, C)\),如果原来的 FD 集合是 \(A \rightarrow B, B \rightarrow C\),那么显然 \(A \rightarrow C\) 也是成立的,但是如果不计算闭包只从原 FD 集合中取出某个 FD,那么就会遗漏这个函数依赖。
这个方法也可以推广到计算所有的函数依赖(也就是函数依赖集的闭包),不过是这个算法的一种特例罢了。
因为任意一个属性都能决定其他两个属性,问题可以实际上转化成构造尽可能少的边使得这个有向图变成强连通图(最小树形图)。
当试图在一个关系中包含过多的信息时,产生的问题称为异常 anmoaly:
-
冗余:信息没有必要重复存储
-
更新异常:如果信息重复存储,那么更新一个信息可能会导致信息不一致
-
删除异常:删除一个信息可能会导致不可挽回的其他信息的丢失
Boyce-Codd范式 BCNF:关系 R 属于 BCNF 当且仅当对于 R 的每一个非平凡函数依赖 \(X \rightarrow Y\),X 都是 R 的一个超键。换言之,每个非平凡函数依赖的左边都必须包含键。
如果一个关系不满足 BCNF,那么我们可以通过分解来消除这种异常。分解的目标是将一个关系分解成多个关系,这些关系都满足 BCNF。
BCNF分解算法:
-
检查 R 是否满足 BCNF,如果满足则结束
-
如果存在 BCNF 违例,假设为 \(X \rightarrow Y\),计算 \(X^+\),选择 \(R_1 = X^+\),\(R_2 = (R - X^+) ∪ X\)
-
计算 \(R_1\) 和 \(R_2\) 上的投影后的函数依赖集
-
递归地对 \(R_1\) 和 \(R_2\) 进行检查
(a)
首先计算全部的函数依赖集
子集 \({A}\) 的闭包是 \({A}^+ = {A}\);子集 \({B}\) 的闭包是 \({B}^+ = {B}\);子集 \({C}\) 的闭包是 \({A, C, D}\),那么\(C \rightarrow A\) 被加入到 FD 中;子集 \({D}\) 的闭包是 \({D}\)
子集 \({A, B}\) 的闭包是 \({A, B, C, D}\),那么 \(A B \rightarrow D\) 被加入到 FD 中;子集 \({A, C}\) 的闭包是 \({A, C, D}\),那么 \(A C \rightarrow D\) 被加入到 FD 中;子集 \({A, D}\) 的闭包是 \({A, D}\),那么 \(A D \rightarrow C\) 被加入到 FD 中;子集 \({B, C}\) 的闭包是 \({A, B, C, D}\),那么 \(B C \rightarrow D, B C \rightarrow A\) 被加入到 FD 中;子集 \({B, D}\) 的闭包是 \({A, B, C, D}\),那么 \(B D \rightarrow A, B D \rightarrow C\) 被加入到 FD 中;子集 \({C, D}\) 的闭包是 \({A, C, D}\),那么 \(C D \rightarrow A\) 被加入到 FD 中
子集 \({A, B, C}\) 的闭包为 \({A, B, C, D}\),那么 \(A B C \rightarrow D\) 被加入到 FD 中;子集 \({A, B, D}\) 的闭包为 \({A, B, C, D}\),那么 \(A B D \rightarrow C\) 被加入到 FD 中;子集 \({A, C, D}\) 的闭包为 \({A, C, D}\);子集 \({B, C, D}\) 的闭包为 \({A, B, C, D}\),那么 \(B C D \rightarrow A\) 被加入到 FD 中
综上所述,所有的 FD 已经求出
关系的键是 \({A, B}, {B, C}, {B, D}\)
违反 BCNF 的 FD:\({C \rightarrow D, D \rightarrow A, C \rightarrow A, CD \rightarrow A}\)
先根据 \(C \rightarrow D\) 分解,得到 \(R_1 = {A, C, D}, R_2 = {B, C}\)
\(R_1\) 的键为 \({C}\), \(D \rightarrow A\) 违反 BCNF,分解得到 \(R_3 = {A, D}, R_4 = {C, D}\)
答案为 \(R_2, R_3, R_4\)
打字太麻烦,剩下的题不写了
一个好的分解,应该具有如下的三个性质:
-
消除异常
-
信息的可恢复,如果可以通过分解的关系恢复原来的关系,那么这个分解含有无损连接 lossless join
-
依赖的保持,如果分解后的函数依赖集合和原来的函数依赖集合一致,那么这个分解含有保持连接性
chase检验是一种检验一个分解是否含有无损连接的方法。核心思想是利用的悬浮元组的思想,尝试从若干子关系连接起来后产生的元组恢复成原来的关系中的对应元组。
容易证明 chase 检验的有效性
无损连接和依赖保持往往不可兼得,不得不在二者之间做出选择。
(d) 对每个子关系计算原 FD 最小基本集的投影
关系 R 属于第三范式 3NF:只要 R 中的每一个非平凡函数依赖 \(X \rightarrow Y\),或者 X 是 R 的一个超键,或者每个属于 Y 但是不属于 X 的属性都是 R 的某个键的成员(通常被称为主属性 prime)
3NF分解算法:
- 找出函数依赖集 F 的一个最小基本集 G
- 对于G中的每一个 FD \(X \rightarrow Y\),把 \(X Y\) 作为分解出的某个关系的模式
- 如果第 2 步分解出的关系的模式均不包含 R 的超键,则增加一个关系,模式为 R 的任意一个键
为什么 3NF 分解算法可以保证函数依赖的保持?容易证明,最小基本集中每个 FD 都对应着分解后的某个关系模式,没有哪个 FD 被“拆分”了。
比如对于关系 \(R(A, B, C)\) \(FD: {A \rightarrow C, B \rightarrow C}\),键为 \({A, B}\),对于 \(A \rightarrow C\) 分解得到 \(R_1(A, C), R_2(A, B)\),显然 \(B \rightarrow C\) 没有得到保持;这个例题也很好展示出了 BCNF 和 3NF 的区别
多值依赖 multivalued dependency MVD:\(A \rightarrow \rightarrow B\) 对于 R 中每个在所有 A 属性上的元组对 t 和 u,能在 R 中找到满足如下条件的元组 v:
-
v 和 t 在 A 上的值相同
-
v 和 u 在 B 上的值相同
-
v 在 R 中不属于 A 或 B 的属性上的值与 u 相同
多值依赖的推导有如下规则:
-
传递规则:如果 \(A \rightarrow \rightarrow B\) 且 \(B \rightarrow \rightarrow C\),那么 \(A \rightarrow \rightarrow C\)
-
结合规则:如果 \(A \rightarrow \rightarrow B\) 和 \(A \rightarrow \rightarrow C\),那么 \(A \rightarrow \rightarrow BC\);注意多值依赖没有分解规则
-
FD升级规则:如果 \(A \rightarrow B\),那么 \(A \rightarrow \rightarrow B\)
-
互补规则:如果 \(A \rightarrow \rightarrow B\),那么 \(A \rightarrow \rightarrow (R - B)\);这可以导出附加平凡多值依赖 more trivial MVD,即 \(R = {A, B}\),那么 \(A \rightarrow \rightarrow B\) 是平凡的
第四范式 4NF:如果 R 中的每一个多值依赖 \(A \rightarrow \rightarrow B\),那么 A 是 R 的一个超键;由于 FD 提升规则,因此可以把第四范式看作是 BCNF 的一个推广形式
4NF分解算法:
-
找出 R 的一个 4NF 违例,假设为 \(A \rightarrow \rightarrow B\)
-
把关系 R 分解为 \(R_1 = A, B\) 和 \(R_2 = A ∪ (R - A - B)\)
-
递归地对 \(R_1\) 和 \(R_2\) 进行检查
打答案手太累了 😦
chase 检验同样可以扩展到 MVD,用来检验一个 MVD 是否在某个关系及其函数依赖集合上成立(等价于已知 t 和 u ,找到一个元组 v)
理论上来说,每次 chase 都会导致表格内元组数量翻倍,那么 chase 是否能够终止?容易证明,因为 chase 不创造新的字母。
第四章 高级数据库模型
在 实体/联系模型 E/R model 中,数据结构用图形化方式表示,用到以下三个主要元素类型:
-
实体 entity:描述了数据库中的对象,用矩形表示
-
联系 relationship:描述了实体之间的关系,用菱形表示
-
属性 attribute:描述了实体的特征,用椭圆形表示
实体之间的联系可以是一对一、一对多、多对多,这些联系可以用箭头表示。如果从实体集 E 到实体集 F 是多对一,那么箭头从 E 指向 F,反之亦然
在一个联系中一个实体集可能出现多次,这是因为实体集可能在关系中承担多个角色,人们给实体集和联系之间的边命名,称之为角色。
不仅实体集可以携带属性,联系也可以携带属性,这些属性称为联系属性。
连接实体集 的产生是为了解决多路联系到二元联系的转换,它的实体被看作是多路联系的联系集的元组。
在一些情况下,一个实体集中可能含有一些实体具有其他实体没有的属性,这个时候把这些实体分离出来当作原实体集的一个子类,用一个叫做 isa 的关系连接,isa 不需要标出箭头,因为其必然是一对一联系。
无标准答案
在使用 E/R 视图设计数据库模式时,需要考虑如下的设计原则:
-
忠实性:设计的模式应该忠实地反映现实世界的情况
-
避免冗余:避免在模式中重复存储信息,这不仅仅占用物理资源,还可能导致更新时隐患
-
简单性:避免添加不必要的多余设计
-
选择正确的联系:选择正确的联系类型,比如一对一、一对多、多对多
-
选择正确的元素种类:选择正确的实体、联系、属性
可以给出在哪种情况下使用属性而不是实体集的一种形式化定义:假设 E 是个实体集。如果要把 E 用一个属性或者几个其他实体集的属性代替:
-
所有与 E 有关的关系都必须有箭头指向 E
-
E 的唯一键是它所有的属性
-
没有联系包含 E 多次
如果条件被满足,可以这样代替实体集 E:
a) 如果从实体集 F 到 E 有多对一联系 R,那么删除 R 并把 E 的属性作为 F 的属性上的值与
b) 如果有多路联系 R 的箭头指向 E,把 E 的属性作为 R 的属性,并删除 E 到 R 的弧
很妙啊这个方法
对于题目给出的要求,可以把它们看作 FD,然后对整个联系进行 3NF 分解
E/R 模型中同样可以体现约束,在一个实体集中键的属性用下划线标出
对于引用完整性约束,用一个圆箭头表示;我们还引入一个新的约束度约束,表示两个实体集之间任意一个实体可悲联系到的实体数目的约束,在边上加不等式表达
有一些实体,想要确认它们的唯一性需要依赖于其他实体(比如只有学校 + 学院才能唯一地标识这个学院),这种情况下可以使用弱实体集 weak entity set。
双矩形表示一个弱实体集,双菱形表示一个多对一的联系(支持联系),有助于提供弱实体集的键
弱实体集 E 的键 的组成为:
-
E 的部分属性
-
从 E 到其他实体集的多对一连接的键属性,这些多对一联系称为 E 的支持联系,从 E 到达的实体集称为支持实体集
某个多对一联系 R 成为支持联系需要满足:
-
R 必须是从 E 到 F 的二元多对一联系
-
R 必须有从 E 到 F 的引用完整性
-
F 提供给 E 的键属性必须是 F 的键,这一过程可以递归进行(当 F 同样是一个弱实体集时)
-
如果从 E 到 F 有多个不同的支持联系,那么每个联系被用来提供一份 F 的键的拷贝
把 E/R 模型可以转化成关系模式:
-
每个实体集可以转化从具有相同属性集的关系
-
联系可以转化为属性集为所连接的实体集的键的关系,如果这个联系有属性,那么这些属性也被包含在关系中
有的时候可以把实体集和联系组合起来:如果存在一个实体集 E 和一个从 E 到 F 的多对一联系 R,可以普通地转化 E R 和 F,但是由于 R 是一个多对一联系,R 中存储的 E 的键属性可以确定 E 中除了键属性之外的所有属性,因此可以把 E 和 R 合并成一个关系,包含:
-
E 的所有属性
-
R 的所有属性
-
F 的键
弱实体集的转化要麻烦一点:
-
弱实体集 E 的转化包含 E 的所有属性,以及 E 的支持实体集的键
-
与弱实体集 W 相连的关系,经转化后所得的关系必须包含 W 的键属性,以及对 W 的键有贡献的支持实体集属性
-
一个支持联系 R 不必被转化为关系(上文所说的 E 和 R 合并成一个关系),想转化也行
子类结构到关系模式的转化:
-
E/R 方式转化,按上文提到的方法忠实地转化
-
面向对象的方法:枚举层次中可能的子树,为每一个子树构造一个可以描述子树中实体的关系
-
允许空值:对一个实体集层次只创建一个关系,这个关系包含了层次中所有实体集的所有属性
第五章 代数和逻辑查询语言
关系被拓展成包而不是集合,这样可以处理重复元组;这样,原有的关系代数上的操作需要做进一步的拓展。
除了原有的算子,我们还会引入一些新的算子:
-
消重复:\(\delta(R)\),消除 R 中的重复元组
-
聚集:SUM, AVG, COUNT, MAX, MIN
-
分组:分组操作符 \(\gamma\) 是组合了分组和聚集操作的一个算子
\(\gamma\) 的语法有些复杂,其下标是一个元素的列表 L:
-
L 中的元素是属性名,表示按照这些属性进行分组
-
应用到一个属性上的聚集操作符,用一个箭头指向新的名字
- 扩展投影:其下标同样是一个元素的列表 L:
-
一个属性名
-
形如 E -> z 的表达式,其中 E 是一个表达式(可以接受属性名进行运算),z 是一个新的属性名
-
排序:\(\tau\),其下标是一个属性名的列表,表示按照这些属性排序
-
外连接:符号的上面多个圆圈
-
外连接 \(\bowtie\) 是连接的一个推广,它保留了连接的所有元组,即使连接的属性值不匹配,会产生悬浮元组
-
左外连接 \(\bowtie_L\) 保留左边的元组,右边的元组如果没有匹配的元组则用 NULL 填充
-
右外连接 \(\bowtie_R\) 保留右边的元组,左边的元组如果没有匹配的元组则用 NULL 填充
-
上述连接对应的 \(\theta\) 连接,可以用 \(\bowtie_{\theta}\) 表示