Chapter 2: Introduction to the Relational Model¶
约 3904 个字 1 张图片 预计阅读时间 13 分钟
关系数据库的结构¶
关系数据库由表(table)的集合构成,每张表被赋予一个唯一的名称。一般来说,表中的一行代表了一组值之间的某种联系。n 个值之间的一种联系在数学上用这些值的一个 n 元组来表示,它也对应于表中的一行。
在关系模型中,术语关系(relation)被用来指代表,而术语元组(tuple)被用来指代行。类似地,术语属性(attribute)指代的是表中的列。
我们用关系实例(relation instance)这个术语来指代一个关系的特定实例,也就是说关系实例包含一组特定的行。
关系是元组的集合(set),元组在关系中出现的顺序是无关紧要的。我们通常按照关系的第一个属性的次序来显示关系。对于关系的每个属性都存在一个允许取值的集合,称为该属性的域(domain)。如果一个域中的元素被认为是不可再分的单元,则该域就是原子的(atomic)。我们要求对所有关系 r 而言,r 的所有属性的域都是原子的。
空值(null value)是一个特殊的值,它表示值未知或并不存在。空值在我们访问和更新数据库的时候会带来很多困难,因此应尽量避免使用空值。
数据库模式¶
当我们谈论数据库时,必须在数据库模式(database schema)和数据库实例(database instance)之间进行区分,前者是数据库的逻辑设计,后者是在给定时刻数据库中数据的一个快照。
关系的概念对应于程序设计语言中变量的概念,而关系模式(relation schema)的概念对应于程序设计语言中类型定义的概念。一般说来,一个关系模式由一个属性列表及各属性所对应的域组成。关系实例的概念对应于程序设计语言中变量的值的概念。
在关系模式中使用公共属性是将不同关系的元组联系起来的一种方式。
码¶
我们必须有一种方式来区分一个给定关系中的不同元组。这种区分是用它们的属性来表示的。也就是说,一个元组的所有属性值必须能够唯一标识元组。换句话说,一个关系中不可以有两个元组在所有属性上取值完全相同。
超码(superkey)是一个或多个属性的集合,将这些属性组合在一起可以允许我们在一个关系中唯一地标识出一个元组。
形式化地,令 \(R\) 表示关系 \(r\) 模式中的属性集合。如果我们说 \(R\) 的一个子集 \(K\) 是 \(r\) 的一个超码,那么在我们考虑关系 \(r\) 的实例时,就限制了其中没有两个可区分元组会在 \(K\) 的所有属性上取值完全相等。也就是说,如果 \(t_1\) 和 \(t_2\) 在 \(r\) 中且 \(t_1 \neq t_2\),则 \(t_1.K \neq t_2.K\)。
超码中可能包含无关紧要的属性。如果 \(K\) 是一个超码,那么 \(K\) 的任意超集也是超码。我们通常只在意这样的超码:它们的任意真子集都不是超码。这样的最小超码称为候选码(candidate key)。存在几个不同的属性集都可以作为候选码的情况。
我们用主码(primary key)这个术语来代表被数据库设计者选中来作为在一个关系中区分不同元组的主要方式的候选码。码(主码、候选码或超码等)是整个关系的一种性质,而不是单个元组的性质。关系中的任意两个不同的元组都不允许同时在码属性上具有相同的值。码的指定代表了现实中的约束。因此,主码也被称作主码约束(primary key constraint)。
习惯上,将一个关系模式的主码属性列于其他属性之前并加下划线,例如 \(classroom(\underline{building}, \underline{room\_ number}, capacity)\)。主码应选择值从不变化或极少变化的属性。
接下来考察关系内容上的另一种类型的约束,称为外码约束。从 \(r_1\) 关系的 \(A\) 属性(集)到 \(r_2\) 关系的主码 \(B\) 的外码约束(foreign-key constraint)表明:在任何数据库实例中,\(r_1\) 中每个元组对 \(A\) 的取值也必须是 \(r_2\) 中某个元组对 \(B\) 的取值。\(A\) 属性集被称为从 \(r_1\) 引用 \(r_2\) 的外码(foreign key)。\(r_1\) 关系也被称为此外码约束的引用关系(referencing relation),且 \(r_2\) 被称为被引用关系(referenced relation)。
在外码约束中,被引用属性(集)必须是被引用关系的主码。更泛化的情况是引用完整性约束,它放松了被引用属性构成被引用关系主码的要求。通常,引用完整性约束(referential integrity constraint)要求引用关系中的任意元组在指定属性上出现的取值也必然出现在被引用关系中至少一个元组的指定属性上。
事实上,外码约束是引用完整性约束的一种特例,其中被引用的属性构成被引用关系的主码。
模式图¶
一个带有主码和外码约束的数据库模式可以用模式图(schema diagram)来表示。下图展示了用于大学组织机构的模式图。每个关系显示为一个框,关系名用蓝色显示在顶部,并且在框内列出了各属性。主码属性用下划线标注,外码约束用从引用关系的外码属性指向被引用关系的主码属性的箭头来表示。我们使用双头箭头而非单头箭头来表示不是外码约束的引用完整性约束。例如下图中从 \(section\) 关系的 \(time\_ slot\_ id\) 指向 \(time\_ slot\) 关系的 \(time\_ slot\_ id\) 的双头箭头线。
关系查询语言¶
查询语言(query language)是用户用来从数据库中请求获取信息的语言。这类语言通常比标准的程序设计语言的层次更高。查询语言可以分为命令式的、函数式的以及声明式的。
- 在命令式查询语言(imperative query language)中,用户指导系统在数据库上执行特定的运算序列以计算出所需的结果;这类语言通常有一个状态变量的概念,状态变量在计算的过程中被更新。
- 在函数式查询语言(functional query language)中,计算被表示为对函数的求值,这些函数可以在数据库中的数据上运行或在其他函数给出的结果上运行;函数没有附带作用,并且它们并不更新程序的状态。
- 在声明式查询语言(declarative query language)中,用户只需描述所需信息,而不用给出获取该信息的具体步骤序列或函数调用,所需的信息通常使用某种形式的数学逻辑来描述。找出获得所需信息的方式是数据库系统的工作。
下文所介绍的关系代数(relational algebra)是一种函数式查询语言。元组关系演算和域关系演算是声明式的。
关系代数¶
关系代数由一组运算组成,这些运算接受一个或两个关系作为输入,并生成一个新的关系作为它们的结果。其中一些运算(如选择、投影和更名运算)称为一元(unary)运算,因为它们只在一个关系上进行运算。另一些运算(如并、笛卡儿积和集差)在一对关系上进行运算,因此称为二元(binary)运算。
由于关系是元组的集合,因此关系不能包含重复的元组。在讨论正式的关系代数时,如集合的数学定义所要求的那样,我们要求去除重复项。
选择运算¶
选择(select)运算选出满足给定谓词的元组。我们使用小写的希腊字母 sigma(\(\sigma\))来代表选择。谓词写在 \(\sigma\) 的下标中。作为参数的关系写在 \(\sigma\) 后的括号内。
为选出 instructor 关系中对应物理系教师的元组,我们写为: $$ \sigma_{dept.name = {Physics}}(instructor) $$
通常,我们允许在选择谓词中使用 \(=, \neq, <, \leq, >, \geq\) 来进行比较。此外,我们可以通过使用连接词 \(and(\wedge), or(\vee), not(\neg)\) 将几个谓词合成一个更长的谓词。
为查找薪水超过 9w 美元的物理系教师,写为: $$ \sigma_{{dept.name} = {Physics} \wedge {salary} > 90000}(instructor) $$
投影运算¶
投影(project)运算是一种一元运算,返回它的参数关系,但滤掉了特定的属性。由于关系是一个集合,任何重复的行都会被删除。投影用大写的希腊字母 pi(\(\Pi\))来表示。将那些希望出现在结果中的属性作为 \(\Pi\) 的下标列出。参数关系写在后面的括号中。
我们希望列出所有教师的 ID、name 和 salary,但我们并不关心 dept_name,则可以写为: $$ \Pi_{ID, name, salary}(instuctor) $$
投影运算 \(\Pi_{L}(E)\) 的基础版本只允许在列表 \(L\) 中出现属性名,该运算的泛化版本则允许在列表 \(L\) 中出现涉及属性的表达式。
我们可以使用以下表达式得到每位教师的月薪: $$ \Pi_{ID, name, salary/12}(instructor) $$
关系运算的复合¶
关系运算的结果本身也是关系。一般来说,由于关系代数运算的结果与其输人具有相同的类型,因此可以将关系代数运算复合在一起成为关系代数表达式(relational-algebra expression)。
查找物理系所有教师的姓名可以写为: $$ \Pi_{name}(\sigma_{dept.name = Physics}(instructor)) $$
笛卡儿积运算¶
笛卡儿积(Cartesian-product)运算用叉号(\(\times\))表示,它允许我们结合来自任意两个关系的信息。我们将关系 \(r_1\) 与 \(r_2\) 的笛卡儿积记为 \(r_1 \times r_2\),表示将 \(t_1\) 与 \(t_2\) 拼接成单个元组。
由于相同的属性名可能既出现在 \(r_1\) 的模式中,也出现在 \(r_2\) 的模式中,因此我们需要设计一种命名机制来在这些属性之间进行区分。在这里,我们通过将属性所属的关系名附加到该属性上来做到这一点。例如,对应于 \(r = instructor \times teaches\) 的关系模式为:
使用此模式,我们可以区分 \(teaches.ID\) 和 \(instructor.ID\)。对于那些仅出现在其中一种模式中的属性,我们通常删除关系名前缀。这种简化并不会导致任何歧义。这样我们可以将 \(r\) 的关系模式写成:
这种命名规范要求作为笛卡儿积运算参数的两个关系具有不同的名称。这一要求在某些情况下会导致问题,我们将使用更名运算来避免这些问题。
一般来说,如果有关系 \(r_1(R_1)\) 和 \(r_2(R_2)\),那么 \(r_1 \times r_2\) 就是这样一个关系 \(r(R)\):它的模式 \(R\) 是 \(r_1\) 和 \(r_2\) 的模式的拼接。关系 \(r\) 包含所有这样的元组 \(t\):对于 \(t\) 存在 \(r_1\) 中的一个元组 \(t_1\) 和 \(r_2\) 中的一个元组 \(t_2\),满足 \(t\) 和 \(t_1\) 在 \(R\) 中的属性上取值相同,并且 \(t\) 和 \(t_2\) 在 \(R\) 中的属性上取值相同。
连接运算¶
考虑关系 \(r(R)\) 和 \(s(S)\),并令 \(\theta\) 为 \(R \cup S\) 模式的属性上的一个谓词。连接(join)运算 \(r \bowtie_\theta s\) 定义如下:
\(\sigma_{instructor.ID = teaches.ID}(instructor \times teaches)\) 可以等价地写为 $$ instructor \bowtie_{instructor.ID = teaches.ID} teaches $$
集合运算¶
为使集合运算在关系代数中有意义,我们约定:
- 必须确保输入运算的两个关系具有相同数量的属性,一个关系的属性数量被称为它的元数(arity)。
- 当属性有关联的类型时,对于每个 \(i\),两个输人关系的第 \(i\) 个属性的类型必须相同。这样的关系被称为相容(compatible)关系。
对于两个相容关系,可以对其进行并(union,\(\cup\)),交(intersection,\(\cap\)),集差(set-difference,\(-\))运算。
为找到在 2017 秋学期、在 2018 春学期或在这两个学期都开设的课程的集合,利用并集可写为:
赋值运算¶
有时通过将一个关系代数表达式中的一部分赋值给临时的关系变量,可以方便地编写该表达式。赋值(assignment)运算用 \(\leftarrow\) 来表示,其工作方式与程序设计语言中的赋值是类似的。
赋值运算并没有为关系代数提供任何额外功能,只是提供了一种表达复杂查询的简便方式。
为找到在 2017 秋学期、在 2018 春学期或在这两个学期都开设的课程的集合,利用赋值可写为:
更名运算¶
不同于数据库中的关系,关系代数表达式的结果并没有可以用来指代它们的名称。在某些情况下,给它们命名是有用的;更名(rename)运算用小写希腊字母 rho(\(\rho\))来表示,它使我们能够做到这一点。给定一个关系代数表达式 \(E\),下述表达式
返回以 \(x\) 命名的表达式 \(E\) 的结果。
关系 \(r\) 本身就是一个(平凡的)关系代数表达式。因此,我们也可以对关系 \(r\) 应用更名运算,以得到新名称下的同一个关系。有些查询需要在查询中不止一次地使用相同的关系;在这种情况下,可以使用更名运算为相同关系的不同出现提供不同的名称。
更名运算的第二种形式如下:假设一个关系代数表达式 \(E\) 的元数为 \(n\)。那么,表达式
返回以 \(x\) 命名的表达式 \(E\) 的结果,并将其属性重命名为 \(A_1, A_2, \cdots, A_n\)。这种形式的更名运算可用于在涉及属性上的表达式的关系代数运算的结果中为属性命名。
等价查询¶
用关系代数来编写查询的方式通常不止一种。对于如下查询:
可考虑一个可替代的查询:
这两条查询是等价(equivalent)的,即它们在任何数据库上都给出相同的结果。
