关系型数据库的理论最早可以追溯到E. F. Codd博士1970年的论文《大型共享数据库的数据关系模型》,在这篇文章里,他总结出了七条抽象的规则,叫做范式(normal form),用来帮助创建设计良好的数据库。
这七条规则的前四条——第一范式(First Normal Form,1NF)、第二范式(2NF)、第三范式(3NF)和Boyce-Codd范式(BCNF)——在大多数情况下已经够用了。
这些范式是非常抽象的,以至于有些开发人员在如何应用它们上存在问题。也许理解范式最好的方式是开始将它们应用于数据,因为规则在你确实有数据要划分的时候才更有作用。在本文中,我会对一个书目示例数据库应用1NF的规则,这些规则在一开始应用的时候是最复杂的。
关于关系型数据库的理论
本文是关系型数据库设计理论系列的第三篇文章。前两篇文章是:
《关系型数据库:理论背后的灵感》
《关系型数据库:使用范式创建数据库》
你会回忆起,1NF的要求是:
多值字段(multivalued field)必须要被移动到另一个表格里。
每个字段必须是原子型的(atomic),或者说要尽量地小。
每个字段都必须有一个要害字(key).
重复的值必须要被移动到另一个表格里。
我将要使用的简单表格是用来保存一些书目信息的。到目前为止,这个Books表格有下面这些字段:
{Title, Author, ISBN, PRice, Publisher, Category}
将多值字段移动到另一个表格
应用1NF的第一步是确保表格没有包含多值字段,从定义可知它能够保存一个以上可能的条目。我们最开始的清单有两个可能会违反这一规则的地方:Author(作者)和Category(分类)。许多书都有多个作者,所以Author字段就会出问题。类似的,一本书可以被归入多个类别。例如《金银岛(Treasure Island)》可以被归为儿童读物、冒险类、经典类,以及其他类等等。
更正这个问题的唯一方法是把这些违反规则的字段转移到另一个表格里。你可能会发现字段在另一个已存在表格里工作得会更好,但是这样的情况非常少见。在大多数情况下,你需要为你所移动的每个字段都创建新的表格。为了满足1NF的要求,我为Author和Category这两个字段创建了两个新的表格,其他的字段都留在Books表格里没有动:
{Title, ISBN, Price, Publisher}
每个字段都必须是原子型的
1NF的下一项要求是说,每个字段都必须是原子型的,这就表示每个字段必须保存可能会有的最小数据元素。这条规则有助于搜索和排序。Author字段在这里再一次出现了问题,因为一个名字可以被分成多条信息。我们需要一个字段用于姓,另一个字段用于名,这会让搜索作者姓名变得轻易得多。
在这一点上,我会更进一步把Authors表格分成至少两个字段:FirstName(名)LastName(姓),那么我数据库的布局就是下面这样的:
Books: {Title, ISBN, Price, Publisher}
Authors: {FirstName, LastName}
Categories: {Category}
每个字段都必须有一个要害字
搜索有重复记录(duplicate record)的表格是很难的;事实上,关系模型不答应表格包含有重复记录。所以,一个表格里字段或者列的值必须是唯一的。唯一性可以通过检查key(要害字)来确定,要害字可以由一个单列或者列的组合构成,这样的列叫做composite key(复合要害字)。
要害字有很多不同的类型:
超要害字(Super key):唯一辨别表格里记录的一个列或者一组列。
备选要害字(Candidate key):包含有确定唯一性所需要的最少列的超要害字。
主要害字(Primary key):用来唯一辨别表格里记录的备选要害字。
备用要害字(Alternate key):没有被选为主要害字的备选键。
外来要害字(Foreign key):表格内匹配同一表格或者另一表格里备选要害字的一个列或者一组列。外来键答应你将一个表格里的记录和另一个表格里的数据相关联。
这里列出来的要害字的类型并不是相互排斥的;一个要害字可以同时被归入多个类。从定义上说,每个表格必须至少有一个主要害字。
要确定我们示例表格的主要害字,就让我们从找到超要害字开始。Authors和Categories表格的超要害字很轻易找到,因为它们的字段非常少,但是Books表格的会稍稍困难一点。尽管两本书有同一个名字的机会几乎没有,但是这不是不可能的,所以我不能把Title(书名)作为Books表格的要害字。两本书来自同一个出版社具有同一个名字或者具有同一个ISBN的机会应该更小,所以我可以从这些可能性中创建一些超要害字。表A列出了这三个表格的所有超要害字:
表A
示例数据库的超要害字
Books表格事实上有更多的超要害字,例如Title、ISBN和Publisher,但是要包含所有这些要害字就又太多了。
找到备选要害字
现在是缩短超要害字列表来找到备选要害字的时候了,这些备选要害字包含有一个字段里满足唯一性所需的最少列。Categories表格在这一点上不存在问题,因为它只有一个字段。Authors表格只有一个超要害字,所以很明显它就是备选要害字。
但是,Books表格有点麻烦,但是在最终的分析里,ISBN字段是备选要害字的最好选择。ISBN字段应该是唯一的,但是由于这些数字是由出版商来指定的,所以还是可能出现使用同一ISBN的两本书。实际情况是,只使用ISBN可能永远也不会碰到问题,但是一旦出现这样的小错误,你的整个数据库可能都会崩溃。因此,我决定把Title和ISBN这两个复合超要害字作为Books表格的备选要害字。
确定主要害字
主要害字只不过是你最后用来唯一辨别表格里每条记录的备选要害字。在做完这些事之后,为每个表格分配主要害字就很轻易了。现在我为每个示例数据库定义了下列表格(星号表示主要害字字段):
Books: {*Title, *ISBN, Price, Publisher}
Authors: {*FirstName, *LastName}
Categories: {*Category, Description}
要注重,Categories包含有一个新的字段——Description。单字段的表格是可以接受的,但是加入了描述文本的字段将有助于更加完全地解释每个分类。
--------------------------------------------------------------------------------
将计数字段(counter field)作为主要害字
大多数关系型数据库治理系统(RDBMS)都会提供一类计数或者自动编号的数据类型,它会为每条记录分配一个连续的数值。尽管这些计数字段会保证你得到一个备选要害字,但是这个要害字对于搜索是毫无疑义的。假如你把计数字段作为主要害字,你至少还需要另一个字段,这样才能以有意义的方式找到记录。
--------------------------------------------------------------------------------
那么Authors又会是什么样的呢?在Authors表格里出现重复的姓和名的机会似乎不多,尤其当你的数据库很小的时候。但是不管怎么说这不是不可能的。你可以为这个表格分配一个计数字段,这肯定会解决唯一性的问题,但是这不会有助于你区别不同的作者。
在这里,解决我们问题最简单的方法是加入某种联系信息,例如电子邮件地址或者作者所居住的州或者地区等信息,但是这还是不够明确。你有可能碰到住在同一个州同名同姓的两个作者。这样的情况不多但不是没有可能,所以最好还是为出错做好预备。你可以考虑加入每个作者的邮件地址,但是为了保持例子的简单,我只加入了州名和邮政编码,并扩展主要害字,让其能够同时包含两个新的字段:
Authors: {*FirstName, *LastName, *State, *ZIP}
在这一点上,这真的是保证Authors表格唯一性的唯一方法。
删除重复值
1NF需要我们满足的最后一条要求是在数据里不能有重复的组(group)。在没有检查真实数据之前,要确定你是否满足了这一要求是相当困难的,但是既然我们已经碰到了,不管怎么样就应该着手解决。之后,假如看到一个值重复了多次,我就会考虑把这个字段移动到一个新的表格里。假如我数据库里的表格通过别的途径被正确地规范化了,那么重新建模将不会是个大问题。事实上,许多数据库应用程序似乎总在不停地扩展
这最后一条规则最明显的违反者是Books表格里的Publisher(出版商)字段。尽管有很多出版商,但是我毫无疑问会发现自己一次又一次地碰到同一个出版商。为了让Books满足1NF,我必须要把这个字段移动到另一个表格里。现在示例数据库看起来像下面这样:
Books: {*Title, *ISBN, Price}
Authors: {*FirstName, *LastName, *State, *ZIP}
Categories: {*Category, Description}
Publishers: {*Publisher}
你可能不会碰到重复的出版商名,但是为了安全起见,你可以决定将自动编号字段作为主要害字。假如这样做的话,你的表格设计应该会像下面这样:
Publishers: {*PublisherID, Name}
最后,示例数据库里的表格看上去都符合1NF的要求了。每个字段都尽可能的小,没有重复的组和多值字段,每个表格都有一个要害字。你可能会问还差什么?究竟,开始的时候只有一个表格,