第5章 关系数据模型和 关系数据库约束   本章作为本书第3部分讨论关系数据库的开篇。关系数据模型最初是由IBM Research的Ted Codd于1970年在一篇经典论文(Codd,1970)中提出的,由于它比较简单并以数学为基础,因此立即引起了人们的注意。该模型使用一个看起来有些类似于值表的数学关系(mathematical relation)概念作为其基本构件,并以集合论和一阶谓词逻辑作为它的理论基础。在本章中,将讨论这种模型的基本特征及其约束。   关系模型的商业实现最初出现于20世纪80年代早期,例如IBM公司的MVS操作系统上的SQL/DS系统和Oracle DBMS。从那时起,在大量的商业系统以及许多开源系统中都实现了该模型。当前流行的商业性关系DBMS(RDBMS)包括DB2(来自IBM)、Oracle(来自Oracle)、Sybase DBMS(现在来自SAP),以及SQL Server和Microsoft Access(来自Microsoft)。此外,还有多个开源系统可用,例如MySQL和PostgreSQL。   由于关系模型的重要性,本书的第3部分都专门用于介绍这种模型以及一些与之关联的语言。在第6章和第7章中,将描述SQL的一些方面,SQL是一种全面的模型和语言,并且是商业性关系DBMS的标准(SQL的额外方面将在其他章中介绍)。第8章将介绍关系代数的操作以及关系演算,它们是与关系模型关联的两种形式化语言。关系演算被认为是SQL语言的基础,而关系代数则广泛用在许多数据库实现的内部构件中,用于查询处理和优化(参见本书第8部分)。   关系模型的其他特性将在本书的后续部分中介绍。第9章将把关系模型数据结构与ER和EER模型(在第3章和第4章中介绍)关联起来,并将介绍用于设计关系数据库模式的算法,这些算法把ER或EER模型中的概念模式映射成关系表示。许多数据库设计和CASE1工具都纳入了这些映射。第4部分的第10章和第11章将讨论用于访问数据库系统的编程技术,以及通过ODBC和JDBC标准协议连接到关系数据库的概念。在第11章中还将介绍Web数据库编程的主题。第6部分的第14章和第15章将介绍关系模式的另一个方面,即函数依赖和多值依赖的形式化约束;这些依赖用于开发基于规范化(normalization)概念的关系数据库设计理论。   在本章中,将重点描述数据的关系模式的基本原理。首先将在5.1节中定义关系模型的建模概念和表示法。5.2节专门用于讨论关系约束,它们被认为是关系模型的一个重要部分,并且在大多数关系DBMS中自动实施它们。5.3节将定义关系模型的更新操作,讨论如果处理违反完整性约束的情况,并将介绍事务的概念。5.4节对本章做了总结。   本章和第8章将重点介绍关系模型的形式化基础,而第6章和第7章则重点介绍SQL实际的关系模型,它是大多数商业和开源关系DBMS的基础。许多概念在形式化模型与实际模型之间是通用的,但是也存在少数区别,我们将指出它们。 5.1 关系模型概念   关系模型将数据库表示为关系的集合。非正式地讲,每个关系都类似于一个值表,或者在某种程度上类似于记录的一个平面(flat)文件。之所以称之为平面文件(flat file),是因为每条记录都具有一个简单的线性或平面结构。例如,图1.2中所示的文件数据库就类似于基本的关系模型表示。不过,如我们很快将看到的,关系与文件之间具有重要的 区别。   当把关系视作值表(table)时,表中的每一行都代表一个相关数据值的集合。表中的一行代表一个事实,它通常对应于现实世界的实体或关系。表名和列名用于帮助解释每一行中的值的含义。例如,图1.2中的第一个表名为STUDENT,因为其中的每一行都表示关于一个特定学生实体的事实。列名(Name、Student_number、Class和Major)指定如何基于每个值所在的列来解释每一行中的数据值。同一列中的所有值都具有相同的数据类型。   在正式的关系模型术语中,将行称为元组,将列标题称为属性,并将表称为关系。数据类型描述了值的类型,通过可能的值域来表示可以出现在每一列中的值。现在将正式定义下面这些术语:域、元组、属性和关系。 5.1.1 域、属性、元组和关系   域(domain)D是原子值的集合。就形式化的关系模型而言,原子(atomic)意指域中的每个值都是不可分的。指定一个域的常用方法是:指定一种数据类型,构成域的值都将取自该数据类型。指定一个域名也是有用的,这样将有助于解释它的值。域的一些示例如下: * Usa_phone_numbers:在美国有效的10位电话号码集合。 * Local_phone_numbers:在美国的一个特定区域内有效的7位电话号码集合。这种本地电话号码的使用正迅速变得过时,并且被标准的10位电话号码所取代。 * Social_security_numbers:有效的9位社会安全号的集合(在美国,这是出于就业、纳税和福利的目的,分配给每个人的唯一标识符)。 * Names:表示人名的字符串集合。 * Grade_point_averages:计算的平均成绩的可能值;每个值必须是0~4的一个实数(浮点数)。 * Employee_ages:公司里的雇员的可能年龄;每个值必须是15~80的一个整数值。 * Academic_department_names:大学里院系名称的集合,例如Computer Science(计算机科学系)、Economics(经济系)和Physics(物理系)。 * Academic_department_codes:院系代码的集合,例如“CS”“ECON”和“PHYS”。   上述示例称为域的逻辑定义。还要为每个域指定数据类型(data type)或格式(format)。例如,可以将Usa_phone_numbers域的数据类型声明为一个形如(ddd)ddd-dddd的字符串,其中每个d都是一个数值型(十进制)数字,并且前3位数字构成一个有效的电话区号。Employee_ages的数据类型是一个15~80的整数。对于Academic_department_names,其数据类型是表示有效系名称的所有字符串的集合。因此,要给域提供名称、数据类型和格式。还可以提供额外的信息,用于解释域中的值;例如,诸如Person_weights之类的数字域应该具有计量单位,例如磅或千克。   关系模式(relation schema)2R用R(A1, A2,…, An)表示,它由关系名称R和属性列表A1, A2, …, An组成。每个属性(attribute)Ai都是某个域D在关系模式R中所扮演的角色的名称。D称为Ai的域,用dom(Ai)表示。关系模式用于描述关系;R称为这个关系的名称(name)。关系的度(degree)或元(arity)是其关系模式的属性个数n。   下面的关系存储关于大学生的信息,它的度为7,其中包含7个属性,用于描述每名学生,如下: STUDENT(Name, Ssn, Home_phone, Address, Office_phone, Age, Gpa)   使用每个属性的数据类型,有时可将该定义写为: STUDENT(Name: string, Ssn: string, Home_phone: string, Address: string, Office_phone: string, Age: integer, Gpa: real)   对于这个关系模式,STUDENT是关系的名称,它具有7个属性。在上面的定义中,显示可以给属性分配诸如string或integer之类的通用类型。更确切地讲,可以为STUDENT关系的其中一些属性指定下面这些以前定义的域:dom(Name) = Names,dom(Ssn) = Social_security_numbers,dom(HomePhone) = USA_phone_numbers3,dom(Office_phone) = USA_phone_numbers,dom(Gpa) = Grade_point_averages。也可以通过属性在关系内的位置来引用它们;因此,STUDENT关系的第二个属性是Ssn,第四个属性则是Address。   关系模式R(A1, A2, …, An)的关系(relation)或关系状态(relation state)4r也可以表示成r(R),它是一个n元元组的集合,即r = {t1, t2,…, tm}。每个n元元组(n-tuple)t是n个值的有序列表,即t =,其中每个值vi(1 ≤ i ≤ n)都是dom (Ai)的一个元素,或者是一个特殊的NULL值(在下面和5.1.2节中将进一步讨论NULL值)。元组t中的第i个值对应于属性Ai,可表示为t[Ai]或t.Ai(如果使用位置表示法,则可表示为t[i])。关系内涵(relation intension)和关系外延(relation extension)这两个术语也经常使用,它们分别用于关系模式R和关系状态r(R)。   图5.1显示了一个STUDENT关系的示例,它对应于刚才指定的STUDENT模式。关系中的每个元组都表示一个特定的学生实体(或对象)。我们将关系显示为一个表,其中将每个元组显示为一行,并且每个属性对应一个列标题,指示该列中的值的角色或解释。NULL值表示对于某个单独的STUDENT元组其值未知或者不存在的属性。 图5.1 关系STUDENT的属性和元组   对于以前的关系定义,可以使用集合论的概念更正式地重新表述如下:关系(或关系状态)r(R)是定义在域dom(A1), dom(A2), …, dom(An)上的度为n的数学关系(mathematical relation),它是定义R的域的笛卡儿积(Cartesian product,用×表示)的子集(subset): r(R) ? (dom(A1) × dom(A2) ×…× (dom(An))   笛卡儿积指定了来自基础域的值的所有可能组合。因此,如果用|D|表示域D中的值的总个数或者基数(cardinality)(假定所有的域都是有限的),那么笛卡儿积中的元组总个数将是: |dom(A1)| × |dom(A2)| ×…× |dom(An)|   所有域的基数的这个乘积表示任何关系状态r(R)中可以存在的可能实例或元组的总数。在所有这些可能的组合中,给定时间的关系状态(即当前关系状态(current relation state))只反映了代表现实世界的特定状态的有效元组。一般而言,随着现实世界的状态发生改变,关系状态也会随之改变,并转换成另一种关系状态。不过,模式R是相对静态的,极少发生改变。例如,对于关系中最初没有存储的新信息,可以添加一个属性来表示它。   可以使多个属性具有相同的域。属性名称指示域的不同角色(role)或解释。例如,在STUDENT关系中,同一个域USA_phone_numbers既扮演了Home_phone的角色,指示学生的家庭电话,又扮演了Office_phone的角色,指示学生的办公室电话。第三个具有相同域的可能属性(未显示)是Mobile_phone。 5.1.2 关系的特征   前面的关系定义暗示:某些特征使关系有别于文件或表。现在将讨论其中一些特征。   关系中的元组顺序   关系被定义为元组的集合。数学上讲,集合的元素当中是无序的;因此,关系中的元组没有任何特定的顺序。换句话说,关系对元组的顺序不敏感。不过,在文件中,记录是物理地存储在磁盘上(或内存中)的,因此记录当中总会存在一种顺序。这种顺序指示文件中的第一条、第二条、第i条和最后一条记录。类似地,当把关系显示为一个表时,将以某种顺序显示行。   元组顺序并不是关系定义的一部分,这是因为关系尝试在逻辑或抽象层次表示事实。在同一个关系上可以指定许多种元组顺序。例如,对于图5.1中的STUDENT关系中的元组,可以按Name、Ssn、Age或其他某个属性的值进行排序。关系的定义不会指定任何顺序:一种排序方式不会优先于另一种排序方式。因此,图5.2中所示的关系被认为与图5.1中所示的关系完全相同。当把关系实现为文件或者显示为表时,可能在文件的记录上或者在表的行上指定特定的排序方式。 图5.2 图5.1中的关系STUDENT,它具有不同的元组顺序   元组内的值排序和关系的替代定义   依据关系的上述定义,n元元组是n个值的有序列表,因此元组中的值排序方式(即关系中的属性顺序)很重要。不过,在更抽象的层次上,只要维持属性与值之间的对应关系,那么属性及其值的顺序并不是那么重要。   可以给出关系的替代定义(alternative definition),它使得在元组中对值进行排序是不必要的。在这个定义中,关系模式R = {A1, A2,…, An}是属性的集合(而不是属性的有序列表),而关系状态r(R)则是映射r = {t1, t2,…, tm}的有限集合,其中每个元组ti都是一个从R到D的映射(mapping),并且D是属性域的并集(union,用∪表示);也就是说,D = dom(A1) ∪dom(A2)∪…∪dom(An)。在这个定义中,对于r中的每个映射t,t[Ai]必须在dom(Ai)中(1≤i≤n)。每个映射ti都称为一个元组。   依据这个将元组作为映射的定义,可以将元组(tuple)视作(<属性>,<值>)对的集合(set),其中每个(<属性>,<值>)对都会给出从属性Ai到dom(Ai)中的值vi的映射的值。属性的顺序并不重要,因为属性名是与它的值一起出现的。根据这个定义,图5.3中所示的两个元组是完全相同的。这在抽象层次上是有意义的,因为确实没有理由使元组中的一个属性值必须出现在另一个值之前。当把属性名和值一起包括在元组中时,就称之为自描述数据(self-describing data),因为每个值的描述(属性名)都包括在元组中。 图5.3 当属性和值的顺序不是关系定义的一部分时,以上两个元组是完全相同的   我们将主要使用关系的第一种定义(first definition),其中关系模式内的属性是有序的,而元组内的值也是类似有序的,因为这样可以大大简化表示法。不过,这里给出的替代定义更通用5。   元组中的值和NULL   元组中的每个值都是一个原子(atomic)值;也就是说,它在基本关系模型的框架内是不可再分的。因此,不允许有复合属性和多值属性(参见第3章)。这种模型有时也称为平面关系模型(flat relational model)。关系模型背后的大量理论是在一种假设的基础上发展起来的,这种假设称为第一范式(first normal form)假设6。因此,必须通过单独的关系来表示多值属性,而复合属性则只能在基本关系模型中通过它们的简单组成属性来表示7。   一个重要的概念是NULL值,它们用于表示一些可能未知或者不适用于某个元组的属性的值。在这些情况下,就使用一个称为NULL的特殊值。例如,在图5.1中,一些STUDENT元组的办公室电话就具有NULL值,因为学生没有办公室(也就是说,办公室电话不适用于这些学生)。另一名学生的家庭电话具有NULL值,这大概是由于他家中没有装电话,或者他家中有电话但是我们不知道它(值未知)。一般来讲,NULL值可能具有多种含义,例如:值未知(value unknown)、值(value)存在但是不可用(not available)或者属性不适用(attribute does not apply)于这个元组(也称为值未定义(value undefined))。例如,如果向STUDENT关系中添加一个属性Visa_status,它只适用于表示留学生的元组,那么将出现最后一种NULL类型。可以为不同含义的NULL值类型设计不同的代码。事实证明,把不同类型的NULL值纳入关系模型操作中比较困难,并且超出了本书介绍的范围。   当与其他值一起进行算术聚合或比较操作时,需要知道NULL值的准确含义。例如,两个NULL值的比较操作将会导致歧义——如果客户A和B都具有NULL地址,并不意味着他们具有相同的地址。在数据库设计期间,最好尽可能地避免NULL值。在第7章和第8章介绍操作和查询时以及在第14章介绍数据库设计和规范化时,将进一步讨论这一点。   关系的解释(含义)   可以将关系模式解释为一个声明或者一种断言(assertion)类型。例如,图5.1中所示的STUDENT关系的模式就断言:一般来讲,学生实体具有Name、Ssn、Home_phone、Address、Office_phone、Age和Gpa。然后,就可以将关系中的每个元组解释为一个事实(fact)或者断言的一个特定实例。例如,图5.1中的第一个元组就断言了如下事实:有一个STUDENT,他的Name是Benjamin Bayer,Ssn是305-61-2435,Age是19等。   注意:一些关系可能表示关于实体的事实,而另外一些关系则可能表示关于关系的事实。例如,关系模式MAJORS (Student_ssn, Department_code)断言了学生主修学科。这个关系中的元组将学生与他或她的主修学科关联起来。因此,关系模型把关于实体和关系(relationship)的事实统一表示成关系(relation)。这样做有时会损害可理解性,因为人们不得不猜测关系表示的是实体类型,还是关系类型。第3章中详细介绍了实体-关系(ER)模型,其中详细描述了实体和关系的概念。第9章中的映射过程说明了如何将ER/EER概念数据模型的不同构造(参见第2部分)转换成关系。   关系模式的一种替代解释是作为一个谓词(predicate);在这种情况下,将把每个元组中的值解释为满足谓词的值。例如,对于图5.1中所示的关系STUDENT中的5个元组来说,谓词STUDENT (Name, Ssn, …)为真(true)。这些元组表示现实世界中的5个不同的命题或事实。在逻辑编程语言(例如Prolog)中,这种解释相当有用,因为它允许在这些语言内使用关系模型(参见26.5节)。一种假设(称为封闭世界假设(closed world assumption))指出:宇宙中唯一正确的事实存在于关系的外延(状态)内。任何其他的值组合都会使得谓词为假(false)。当我们基于8.6节中的关系演算考虑关系上的查询时,这种解释就是有用的。 5.1.3 关系模型表示法   在我们的介绍中将使用以下表示法: * 度为n的关系模式R表示为R(A1, A2,…, An)。 * 大写字母Q、R、S表示关系名称。 * 小写字母q、r、s表示关系状态。 * 字母t、u、v表示元组。 * 一般来讲,关系模式的名称(例如STUDENT)也指示该关系中当前元组的集合,即当前关系状态,而STUDENT(Name, Ssn, …)仅指示关系模式。 * 可以利用关系名称R来限定一个属于它的属性A,其方法是使用点表示法R.A。例如,STUDENT.Name或STUDENT.Age。这是由于不同关系中的两个属性可能使用相同的名称。不过,特定关系中的所有属性名称都必须是不同的。 * 关系r(R)中的n元元组t用t = 表示,其中vi是与属性Ai对应的值。下面的表示法指示元组的分量值(component value): * t[Ai]和t.Ai(有时是t[i])都指示属性Ai在t中的值vi。 * t[Au, Aw,…, Az]和t.(Au, Aw,…, Az)都指示t中与列表中指定的属性对应的值 的子元组,其中Au, Aw,…, Az是R中的属性列表。   例如,考虑图5.1中所示的STUDENT关系,对于元组t = ,具有t[Name] = ,以及t[Ssn, Gpa, Age] = < '533-69-1238', 3.25, 19>。 5.2 关系模型约束和关系数据库模式   迄今为止,讨论了单个关系的特征。在关系数据库中,通常会有许多关系,并且这些关系中的元组通常以多种方式相关联。整个数据库的状态将对应于某个特定时间点上该数据库的所有关系的状态。对于数据库状态中的实际值,一般会有许多限制或约束(constraint)。这些约束源于数据库所表示的微观世界中的规则,如1.6.8节中所讨论的那样。   在本节中,将讨论可以在关系数据库上以约束形式对数据指定的各种限制。数据库上的约束一般可以分为以下3个主要类别:   (1)数据模型中固有的约束。我们把这些约束称为基于模型的固有约束(inherent model-based constraint)或者隐式约束(implicit constraint)。   (2)可以在数据模型的模式中直接表示的约束,通常用DDL(数据定义语言,参见2.3.1节)指定它们。我们把这些约束称为基于模式的约束(schema-based constraint)或者显式约束(explicit constraint)。   (3)不能在数据模型的模式中直接表示的约束,因此必须通过应用程序或者其他某种方式来表示和执行它们。我们把这些约束称为基于应用程序的约束(application-based constraint)或语义约束(semantic constraint),也称为业务规则(business rule)。   5.1.2节中讨论的关系特征是关系模型的固有约束,属于第一类约束。例如,关系不能具有重复元组的约束就是一个固有约束。本节中讨论的约束属于第二类约束,即可以通过DDL在关系模型的模式中表示的约束。第三类约束更一般,与属性的含义和行为相关联,并且难以在数据模型内表示和执行,因此通常在执行数据库更新的应用程序内检查它们。在一些情况下,可以利用SQL把这些约束指定为断言(assertion)(参见第7章)。   另一类重要的约束是数据依赖,包括函数依赖和多值依赖。它们主要用于测试关系数据库设计的好坏,并且用在称为规范化的过程中,在第14章和第15章中将讨论相关内容。   基于模式的约束包括域约束、键约束、NULL值约束、实体完整性约束以及参照完整性约束。 5.2.1 域约束   域约束指定在每个元组内,每个属性A的值都必须是一个来自域dom(A)的值。在5.1.1节中讨论了如何指定域。与域关联的数据类型通常包括整数的标准数值数据类型(例如短整型、整型、长整型)和实数的标准数值数据类型(浮点型和双精度浮点型),还包括字符、布尔型、定长字符串和变长字符串,以及日期、时间、时间戳和其他特殊的数据类型。还可以通过某种数据类型的值的一个子范围来描述域,或者将其描述为一种枚举数据类型,其中明确列出了所有可能的值。这里将不会详细描述这些内容,6.1节中将讨论由SQL关系标准提供的数据类型。 5.2.2 键约束和NULL值约束   在形式化的关系模型中,将关系定义为元组的集合。依据定义,集合中的所有元素都是不同的;因此,关系中的所有元组也必须是不同的。这意味着任意两个元组对于它们的所有属性都不可能具有相同的值组合。通常,关系模式R还具有其他的属性子集(subset of attributes),它们具有如下性质:R的任何关系状态r中的任意两个元组对于这些属性都不应该具有相同的值组合。假设利用SK表示这样一个属性子集;那么对于R的关系状态r中任意两个不同的元组t1和t2,都将具有如下约束: t1[SK] ≠ t2[SK]   任何这样的属性集合SK都被称为关系模式R的超键(superkey)。超键SK指定一个唯一性约束,即R的任何状态r中的两个不同的元组不能具有相同的SK值。每个关系至少具有一个默认的超键:即它的所有属性的集合。不过,超键可以具有冗余的属性,因此一个更有用的概念是键,它没有冗余性。关系模式R的键(key)k是R的一个超键,它具有一个额外的性质:如果从K中删除任何属性A,将会得到属性集合K′,它将不再是R的超键。因此,键满足以下两个性质:   (1)关系的任何状态中的两个不同的元组对于键中的(所有)属性不能具有完全相同的值。这种唯一性也适用于超键。   (2)键是最小的超键(minimal superkey),也就是说,不能从此超键中删除任何属性而仍能保持唯一性约束。这种最小性是键所必需的,但对于超键来说则是可选的。   因此,键也是超键,反之则不然。超键可能是键(如果它是最小的话),也可能不是键(如果它不是最小的话)。考虑图5.1中的STUDENT关系。属性集合{Ssn}是STUDENT的键,因为任意两个学生元组都不能具有相同的Ssn值8。包括Ssn的任何属性集合(例如,{Ssn, Name, Age})都是一个超键。不过,超键{Ssn, Name, Age}并不是STUDENT的键,因为从集合中删除Name或Age,或者同时删除它们二者,剩下的仍然是一个超键。一般来讲,由单个属性构成的任何超键也是一个键。具有多个属性的键必须要求它的所有属性一起具有唯一性。   键属性的值可用于唯一地标识关系中的每个元组。例如,Ssn值305-61-2435唯一地标识STUDENT关系中与Benjamin Bayer对应的元组。注意:构成键的属性集合是关系模式的一个性质;关系模式的每种有效的关系状态都应该保持这种约束。键是通过属性的含义确定的,而性质是非时变(time-invariant)的:当在关系中插入新的元组时,它必须继续保持这种性质。例如,我们不能并且也不应该将图5.1中的STUDENT关系的Name属性指定为键,因为在一种有效状态中的某个时间点,可能有两名学生具有完全相同的名字9。   一般来讲,一种关系模式可能具有多个键。在这种情况下,每个键都称为候选键(candidate key)。例如,图5.4中的CAR关系具有两个候选键:License_number和Engine_serial_number。通常将候选键之一指定为关系的主键(primary key),这个候选键的值用于标识关系中的元组。本书使用如下约定:构成关系模式的主键的属性将加下画线,如图5.4中所示。注意:当关系模式具有多个候选键时,可以任意选择其中一个作为主键;不过,通常选择具有单个或较少属性的主键更好一些。其他候选键将被指定为唯一键(unique key),并且不加下画线。 图5.4 CAR关系,具有两个候选键:License_number和Engine_serial_number   属性上的另一个约束将指定是否允许使用NULL值。例如,如果每个STUDENT元组的Name属性必须具有一个有效的非NULL值,那么STUDENT的Name将被约束为NOT NULL。 5.2.3 关系数据库和关系数据库模式   迄今为止讨论的定义和约束适用于单个关系及其属性。关系数据库通常包含许多关 系,并且关系中的元组还会以多种方式相关联。在本节中,将定义关系数据库和关系数据库模式。   关系数据库模式(relational database schema)S是关系模式的集合S = {R1, R2,…, Rm}和完整性约束(integrity constraint,IC)的集合。S的关系数据库状态(relational database state)10DB是关系状态的集合DB = {r1, r2,…, rm},其中每个ri是Ri的一个状态,并且ri关系状态满足IC中指定的完整性约束。图5.5显示了一种关系数据库模式,我们称之为COMPANY = {EMPLOYEE, DEPARTMENT, DEPT_LOCATIONS, PROJECT, WORKS_ON, DEPENDENT}。在每种关系模式中,加下画线的属性都表示主键。图5.6显示了一种与COMPANY模式对应的关系数据库状态。在本章中将使用这种模式和数据库状态,并且在第4~6章中利用不同的关系语言开发示例查询时也将使用它们(可以对这里显示的数据进行扩展,用于加载为本书配套Web站点上填充数据的数据库,也可用于一些章节末尾的动手项目练习题)。 图5.5 COMPANY关系数据库模式的模式图