第五章:设计数据库

在 Visual FoxPro 中,可以使用数据库组织和建立视图间的关系。数据库不但提供了存储数据的结构,而且还有很多其他的好处。在使用数据库时,您可以在表一级进行功能扩展,例如创建字段级规则和记录级规则、设置默认字段值和触发器等,还可以创建存储过程和表之间的永久关系。此外,使用数据库还能访问远程数据源,并可创建本地表和远程表的视图

在本章中,我们将以 Tasmanian Traders 示例数据库(附带其他一些示例数据库)的设计过程为例一步一步地对如何设计 Visual FoxPro 数据库中的表做一个简要的指导。Tasmanian Traders 的示例数据库 TASTRADE.DBC 保存在 Visual Studio …\Samples\Vfp98\Tastrade\Data 目录中。

有关数据库设计完成之后如何创建 Visual FoxPro 数据库的详细内容,请参阅第六章“创建数据库”。有关创建 Visual FoxPro 表的详细内容,请参阅第七章“处理表”

本章内容要点:

数据库设计步骤

如果您使用一个可靠的数据库设计过程,就能迅速、高效地创建一个设计完善的数据库,为您访问所需信息提供方便。在设计时打好坚实的基础,设计出结构合理的数据库,会节省日后整理数据库所需的时间,并使您更快地得到精确结果。

注释 Visual FoxPro 中的术语“数据库”和“表”不是同义词。“数据库”(.dbc 文件)指的是关联的数据库,它是一个或多个( .dbf 文件)或视图信息的容器。

理解数据库设计过程的关键在于理解关系型数据库管理系统(如 Visual FoxPro)保存数据的方式。为了高效准确地提供信息,Visual FoxPro 将不同主题的信息保存到不同的表中。例如,用一个表保存雇员的信息,而用另一个表保存销售的信息。

如果数据组织得当,就能把数据库设计得相当灵活,可以用很多方法组合和提供信息,例如,可以打印一个报表,报表中的信息来自 Employee 和 Orders 两个独立的表。

通过将信息拆分入表,来增加数据库的灵活性

在设计数据库的时候,首先分离那些需要作为单个主题而独立保存的信息,然后告诉 Visual FoxPro 这些主题之间有何关系,以便在需要时把正确的信息组合在一起。通过将不同的信息分散在不同的表中,可以使数据的组织工作和维护工作更简单,同时也易保证建立的应用程序具有较高的性能。

下面是设计数据库的步骤,本章稍后将对每一步进行详细介绍。

  1. 确定建立数据库的目的 这有助于确定 Visual FoxPro 保存哪些信息。

  2. 确定需要的表 在明确了建立数据库的目的之后,就可以着手把信息分成各个独立的主题,例如 Employee 或 Orders 等。每个主题都可以是数据库中的一个表。

  3. 确定所需字段 确定在每个表中要保存哪些信息。在表中,每类信息称作一个字段浏览表时在表中显示为一列。例如,在 Employee 表中,可以有这样两个字段:Last_name 和 Hire_date 。

  4. 确定关系 分析每个表,确定一个表中的数据和其他表中的数据有何关系。必要时,可在表中加入字段或创建一个新表来明确关系。

  5. 设计求精 对设计进一步分析,查找其中的错误。创建表,在表中加入几个示例数据记录,看能否从表中得到想要的结果。需要时可调整设计。

在最初的设计中,不要担心发生错误或遗漏东西。这只是一个初步方案,您可在以后对设计方案进一步完善。在完成初步设计后,可利用示例数据对表单、报表的原型进行测试。Visual FoxPro 很容易在创建数据库时对原设计方案进行修改。可是在数据库中输入了数据或连编表单和报表之后,再要修改这些表就困难得多。正因如此,在连编应用程序之前,应确保设计方案已经考虑得比较全面。

分析数据需求

Visual FoxPro 数据库设计的第一步是明确数据库的目的和如何使用。 也就是说您需要从数据库中得到哪些信息。明确目的之后,就可以确定您需要保存哪些主题的信息(表),以及每个主题需要保存哪些信息(表中的字段)。

请和数据库的使用人员多交换意见,推敲那些需要数据库回答的问题,勾划出要生成的报表,收集当前用来记录数据的表单。所有这些信息在后面的设计步骤中都要用到。

示例:如何跟踪销售情况和存货情况

假设有一家 Tasmanian Traders 进出口公司,这家公司在全世界销售特色食品,要建立一个跟踪本公司销售情况和存货信息的数据库。

首先列出需要数据库回答的问题清单。如:上个月特色食品的销售量是多少?最常光顾的顾客住在何处?谁是最畅销食品的供应商?等等。

接下来,收集所有表单和报表。这些表单和报表包含了应该由数据库提供的各种信息。公司现在使用书面报表记录已订购的产品,使用订单记录新的订货信息。下面就是这两个文件的示例。

表单和报告显示了对数据库的数据需求

除此之外,这家公司还经常需要打印顾客、雇员和供应商的邮政标签。

在收集了这些信息之后,便可以进行下一步工作了。

将需求分类放入表

确定数据库中的是数据库设计过程中技巧性最强的一步。因为根据您想从数据库中得到的结果(包括要打印的报表、要使用的表单、要数据库回答的问题)不一定能得到如何设计表结构的线索,它们只是告诉您需要从数据库得到的东西,并没有告诉您如何把这些信息分门别类地加到表中去。

以前面的订单为例,它除了包括一些顾客的信息(如顾客的地址和电话号码)以外,还提供了有关订货情况的信息。这个表单确实提供了许多需要在数据库中保存的信息,尽管所有信息都在同一表单中,仍可将它们存储在不同的表中,这样容易解决一般数据的完整性问题。

同一信息只保存一次将减少出错的可能性 例如,若只使用一个表存储所有订单的信息, 假设某位顾客有三个不同的订单,您可以在数据库中加入三次该顾客的地址和电话号码(每个订单一次),但这样会增加数据输入出错的可能性。

Customer 表一次性存储地址信息

而且,如果顾客更换了地址,那么您要么接受矛盾的信息,要么查找并更改表中顾客的每一个销售记录。实际上,更好的解决办法是创建一个顾客表,顾客的地址在数据库中只保存一次。以后如果要更改数据,只要更改一次即可。

防止删除有用信息 假设有一位新顾客发出一个订单后,又取消了这个订单。这样,当从包含顾客信息和订货信息的表中删除这个订单时,同时也删掉了顾客的姓名及地址。可是您又想把这个新顾客保存在数据库中,以便能把下一个价目表送给他。因此,最好的解决办法仍然是把顾客的信息放在单独的顾客表中,这样就可以做到只删除订单信息而不删除顾客信息。

请仔细研究需要从数据库中取出的信息,并把这些信息分成各种基本主题(例如顾客、雇员、销售的产品、提供的服务等等),每个主题都是一个独立的表。

提示 把信息划分成表的方法之一是研究每种信息,确定每种信息的实际内容。例如,在 Tasmanian Traders 订单中,顾客地址不属于销售信息,而属于顾客信息,这表明需要有一个单独的顾客表。在 Products on order 报表中,供应商的电话号码并不属于存货信息,而属于供应商信息,这表明需要有一个单独的供应商表。

示例:设计 Tasmanian Traders 数据库中的表

在 Tasmanian Traders 的 orders 表和 Products on order 报表中,包括了下面这些主题的信息:

从这个列表中,可以看出数据库中表的概貌以及每个表的一些字段。

Tasmanian Traders 数据库所需的表和字段的草图

尽管最终的 Tasmanian Traders 数据库可能还需要包含其他一些表,但可以从这个列表开始着手设计。在本章稍后,您会看到如何加入其他表来改进设计。

确定所需字段

为了确定表的字段,首先决定需要在表中了解有关人、物或事件的哪些信息。可以把字段看作是表的属性。表中每个记录(或每行)包含了同样的字段或属性集合。例如,customer 表中的 address 字段记录了顾客的地址。表中每个记录项记录了一个顾客的信息,而 address 字段记录了该顾客的地址。

确定字段

下面是确定字段的几点技巧:

每个字段直接和表的主题相关 描述另一个主题的字段应属于另一个表。本章稍后会介绍如何定义表之间的关系,您将看到如何用多个表的字段进行数据组合。而现在,必须确保一个表中的每个字段直接描述该表的主题。如果多个表中重复同样的信息,这表明在某些表中有不必要的字段。

不要包含可推导得到或需计算的数据 多数情况下,不必把计算结果存储在表中,因为要看结果时可用 Visual FoxPro 进行计算。例如,本章前面列出的 order 表单显示了 Tasmanian Traders 数据库中第一类订货的总价格,可是任何一个 Tasmanian Traders 表中都没有 Extended Price subtotal 这个字段,只是在 Order_Line_Items 表中添加了两个数量字段,存储已订购产品的数量以及每个已订货项的单位价格。利用这些数据,每次打印订单时,Visual FoxPro 都会计算出分类汇总价格,因此不需要在表中包含 Extended Price subtotal 这个字段。

收集所需的全部信息 在设计时很容易忽略重要的信息,这时应回到设计的第一步 — 信息收集。检查书面的表单和报表,确保过去所需的信息都已包括在 Visual FoxPro 表中,或者可由这些表计算出来。重新思考需要 Visual FoxPro 回答的问题:Visual FoxPro 能否使用表中的信息找到所有答案?是否有保存唯一数据的标识字段(例如 customer ID)?哪个表包含了组合一份报表或表单所需的信息?有关标识关键字段和建立各表之间关系的详细内容,请参阅本章稍后的“使用主关键字段”“确定关系”部分。

以最小的逻辑单位存储信息 您可能会想把产品名和对产品的描述一起存入字段。如果一个字段中结合了多种信息,以后要获取单独的信息就会很困难,应尽量把信息分解成比较小的逻辑单位(例如,为产品名、产品类别和产品描述创建不同的字段)。

示例:在 Products 表中添加字段

Tasmanian Traders 公司销售从世界各地进口的特色食品。雇员用 Products On Order 报表了解已订购产品的情况。

产品清单的追踪报告

这个报表表明,保存产品销售信息的 Products 表必须包括产品名、存货数量和订货数量等字段。但供应商名称和电话号码字段又怎么办呢?要生成此报表,Visual FoxPro 需要知道每个产品的供应商。

包括供应商名称字段和电话号码字段的 Supplier 表草图

要解决这问题,您可以创建一个 Supplier 表,其中包含的两个字段分别记录供应商的名字和电话号码,而不用把这些信息存放在 Products 表中。在下面的步骤中您还将学习如何在 Products 表中添加一个字段,以此标识所需的供应商信息。

使用主关键字段

象 Visual FoxPro 这样的关系型数据库管理系统,其功能强大之处在于:它能够迅速查找存储在多个独立表中的信息并组合这些信息。为使 Visual FoxPro 更有效地工作,数据库的每个表都必须有一个或一组字段可用以唯一确定存储在表中的每个记录,通常使用唯一的标识号作为这样的字段(例如,雇员 ID 号或系列号)。在数据库术语中,这一信息称作表的主关键字。Visual FoxPro 利用主关键字迅速关联多个表中的数据,并把数据组合在一起。

如果一个表已经有了唯一的标识符(例如,您自己为库存中的各项产品设定的产品号),那么可以用这个产品号作为表的主关键字,但必须保证该字段的值对每个记录都是不同的 ---- Visual FoxPro 不允许在主关键字段中有重复的值。例如,不要使用人名作为主关键字,因为人名并非唯一,在同一个表中,很容易出现两人同名的情况。

在选择主关键字段时,请记住以下几点:

示例:为 Products 表设置主关键字

Tasmanian Traders 公司的 Products 表的主关键字是产品 ID 号。因为每个产品号标识一个不同产品,所以两个产品不能有同样的产品号。

Products 表的主关键字是 Product_id 字段

在某些情况下,有可能需要把两个或更多的字段连在一起作为表的主关键字。例如,Tasmanian Traders 公司数据库的 Order_Line_Items 表使用下列两个字段作为它的主关键字:Order_id 和 Product_id。在下一步中,您就会明白为什么这样做。

确定关系

到目前为止,已经把信息分成了各个。现在还需要有一种方法,可以使 Visual FoxPro 将这些表中的内容重新组合,得到有意义的信息。例如,下面的表单就包含了从几个表中得到的信息。

Order Entry 表单使用了多个表中的信息

Visual FoxPro 是一个关系型数据库管理系统。也就是说,在每个独立的表中存储的数据之间有关系。您可以在这些表之间定义关系,而 Visual FoxPro 可以利用这些关系来查找数据库中有联系的信息。

例如,假设您想给一名雇员打电话,了解他的销售情况。雇员的电话号码记录在 Employee 表中,销售情况记录在 Orders 表中。您只需告诉 Visual FoxPro 要了解哪方面的销售情况,Visual FoxPro 就能根椐两个表间的关系查找到电话号码。这是因为 Employee 表的主关键字 Employee_id 也是 Orders 表的一个字段。在数据库术语中,Orders 表中的 Employee_id 字段称作“外部关键字”,因为它是另外一个表(或称外部表)的主关键字。

Employee_id 字段既是 Employee 表的主关键字,又是 Orders 表的外部关键字

因此,要建立两个表(表 A 和表 B)的关系,可以把其中一个表的主关键字添加到另一个表中,使两个表都有该字段。但如何确定该使用哪个表的主关键字呢?要正确地建立关系,首先必须明确关系的实质。表之间有三种关系:

本节后面的示例描述了每种关系,并说明了如何设计表,以便 Visual FoxPro 能正确地关联数据。每个示例的目的在于解释如何确定表间的关系,以及表中哪些字段支持这些关系,但并不介绍如何使用 Visual FoxPro 界面来建立表之间的关系。

示例:创建“一对多”关系

一对多关系是关系型数据库中最普通的关系。在一对多关系中,表 A 的一个记录在表 B 中可以有多个记录与之对应,但表 B 中的一个记录最多只能有一个表 A 的记录与之对应。

例如,Tasmanian Traders 公司数据库中的 Category 表和 Products 表就是一对多的关系。

Category 表和 Products 表之间的关系是“一对多”关系。

要建立这样的关系,就要把关系中“一方”的主关键字字段添加到“多方”的表中。在关系中,“一方”用主关键字或候选索引关键字,而“多方”使用普通索引关键字。在本例中,需要把 Category 表中的 Category_id 字段加到 Products 表中,因为“一”类产品中包含了“多”个产品。Visual FoxPro 使用类别 ID 标号确定每个产品所属的类别。

有关创建索引关键字的详细内容,请参阅第七章“处理表”

示例:创建“多对多”关系

多对多关系中,表 A 的一个记录在表 B 中可以对应多个记录,而表 B 的一个记录在表 A 中也可以对应多个记录。这种情况下,在向 Visual FoxPro 正确指定关系之前,需要改变数据库的设计。

要确定表间的多对多关系,对构成关系的双方进行了解是非常重要的。例如,考虑 Tasmanian Traders 公司事务中订单和产品之间的关系。一张订单可以包括多项产品,因此对于 Orders 表中的每个记录,在 Products 表中可以有多个记录与之对应。同样,每项产品也可以出现在许多订单中,因此对于 Products 表中的每个记录,在 Orders 表中也有多个记录与之对应。

Orders 表和 Products 表之间的关系是“多对多”关系

两个表的主题 -- 订单和产品 — 具有多对多关系,这提出了数据库设计中的一个问题。要理解这个问题,可想象一下,如果在 Orders 表中加入 Product_id 字段来建立两表之间的关系,那将会出现什么问题。如果每次订货都不止一种产品,那么在 Orders 表中,每个订单都需要多个产品记录,而对和同一订单有关的每个产品记录,它都需要重复相同的订单信息 -- 这说明,低效的设计会导致不准确的结果。如果把 Order_id 字段加入到 Products 表中也会遇到同样的问题 -- 在 Products 表中每个产品都有多个订单记录。应该怎样解决这个问题呢?

答案就是创建第三个表,把多对多的关系分解成两个一对多关系。这第三个表就称作“纽带表”,因为它在两表之间起着纽带的作用。可以把两个表的主关键字都放在这个纽带表中。

Order_Line_Items 表在 Orders 表和 Products 表间创建了“一对多”的连接

纽带表可能只包含了它所连接的两个表的主关键字,或者,如在 Order_Line_Items 表中,纽带表可包含其他信息。

Order_Line_Items 表中的每个记录代表了一个订单中的一项产品,我们可以称之为行项。Order_Line_Items 表的主关键字由两个字段(来自 Orders 表和 Products 表的外部关键字)组成。单独的 Order_id 字段不能作为这张表的主关键字,因为一个订单可能会包括许多产品,订单的 ID 号在一个订单中的每一产品项都要重复,这样该字段就不能保持唯一。单独的 Product_id 字段也不能作为主关键字,因为一个产品可能出现在许多不同的订单中。但在纽带表中,两个字段连在一起就能使每个记录有唯一值。纽带表不需要自己的主关键字

在 Tasmanian Traders 数据库中,Orders 表和 Products 表并非直接联系,它们通过 Order_Line_Items 表间接联系。订单和产品间的多对多关系在数据库中采用两个一对多关系来代替:

示例:创建“一对一”关系

一对一关系中,表 A 的一个记录在表 B 中只能对应一个记录,而表 B 中的一个记录在表 A 中也只能有一个记录与之对应。这种关系并不经常使用,也需要在设计数据库时做某些更改。

两表间的一对一关系不经常使用。因为在许多情况下,两个表的信息可以简单地合并成一个表。例如,假设要创建一个 Ping-Pong Players(乒乓球运动员)表,用来记录 Tasmanian Traders 公司乒乓球比赛活动的信息。由于乒乓球运动员都是 Tasmanian Traders 的雇员,这个表和 Tasmanian Traders 数据库中的 Employee 表就是一对一的关系。

Employee 表和 Ping-Pong-Players 表之间的关系是“一对一”关系

Ping-Pong Players 表记录的是临时活动,活动结束后便不再需要此信息。另外,并非所有雇员都打乒乓球。如果这些字段出现在 Employee 表中,就有可能这些字段值在许多记录项中都是空的,因此有必要创建一个单独的表。

如果在数据库中存在一对一关系,那就要考虑一下是否能把这些信息合并到一个表中。例如,在雇员表中,每个雇员都有一个经理,经理也是雇员。可以在表中增加一字段,记录经理特有的 ID 号,在查询视图中使用自联接,可以把经理的信息合并到雇员表中,而不用分隔表来解决一对一关系。如果出于某种原因不想这样做,可按下面的方法建立一对一关系:

设计优化

确定了所需要的表、字段和关系之后,应该来研究一下设计方案,并且检查可能存在的缺陷。

设计数据库时可能会遇到一些缺陷。这些常见问题可能会使数据难于使用和维护。

先创建表,然后指定表间的关系,在每个表中输入几个数据记录,看看能否利用数据库找到所需的答案。再粗略地创建一些表单和报表,看看能否显示所期望的数据,找出并消除不必要的重复数据。

在试验最初的数据库时,很可能会发现需要改进的地方。下面是需要检查的几个方面:

确定了要做的修改之后,就可以修改表和字段,来改进设计方案。有关修改表的详细内容,请参阅第七章“处理表”

示例:改进 Products 表

Tasmanian Traders 库存产品有一个一般性的分类,例如饮料、调味品以及海产品等。Products 包含了显示每种产品类别的字段

含有 Category_name 字段的 Products 表

假设在数据库的检查和改进过程中,Tasmanian Traders 公司决定在类别名称基础上再添加对分类的描述。一种方案是在 Products 表中加入 Category Description 字段,这就需要对列入各类的每种产品进行重复的分类描述,不是一个好方案。

较好的方法是把 Category 当作数据库记录的一个新主题,有它自己的表和主关键字,这样就可以把 Category 表中的主关键字添加到 Products 表中,作为外部关键字

Category 表有效地存储了分类信息

Category 表和 Products 表有一对多关系:每类可以包含多种产品,但是每种产品只属于一个类别。

示例数据库图解

本节的数据库图解对您设计自己的数据库会有所帮助。在 Visual FoxPro 中不包含这些数据库,在此只是为您提供一些创建数据库的范例。

Appointments 数据库

此数据库是一个职业办公室用于安排约会的数据库,做适当修改便可用于医生、牙医、律师或会计师的办公室。Appointments 表使用多个字段的主关键字来唯一标识每次约会。这个关键字(即“client_sta”索引),是按照 client_id 字段和 date_start time 字段的组合表达式创建的索引。

Appointments 数据库示例

Personnel 数据库

这个数据库存储人员信息。Job History 表存储有关每次雇用或晋升的信息,所以对每个雇员可以有很多记录。

Personnel 数据库示例

Library 数据库

此数据库存储了有关图书馆以及读者借书情况的信息。请注意,Books 表和 Authors 表之间,Books 表和 Subjects 表之间是多对多关系

Library 数据库示例