这篇文章是楼梯系列的一部分:楼梯到数据库设计
设计和创建数据库的新任务?Joe Celko是所有关于SQL的最广泛阅读的作家之一,他解释了这些基础知识。像往常一样,他偶尔会给最老练的数据库专业人士带来惊喜。乔连续四年成为DBMS杂志读者选择奖的获得者。他曾在美国、英国、北欧国家、南美和非洲教过SQL。他在ANSI /ISO SQL标准委员会任职10年,并为SQL - 89和SQL - 92标准做出了贡献。
有几种类型的表,每个表都有对规则和完整性约束的特殊要求。无论需求如何,表级约束将确保规则被强制执行,数据完整性得到维护。
在第一级,我们将数据元素命名为数据元素,并将其分类。在第二级中,我们使用SQL中的数据类型和约束对数据元素进行建模,以提供行。在第三级,我们将把这些行放到表中。表不仅仅是一组以一个名字组合在一起的行。
列只能在表中出现一次。这是有意义的;如果你记录了别人的鞋码两次,那么最好的情况是多余的,而在最坏的情况下,当列不一致时,就会显得模棱两可。现在我们可以在每行的列中有表级检查约束。它们与我们之前的一列检查约束没有太大的不同。它们可以被命名,并将出现在CREATE TABLE语句的列声明列表中,而不是附加到任何一行。例如:
不要将约束合并成一个巨大的CHECK()子句通常是个好主意。错误消息将包含约束名称,因此单独的约束将使您更好地了解一个名为“Bad_Things_Happened”约束的单个奇怪出错的情况。
继续我们对冗余的仇恨,在表级,我们希望每一行都是独一无二的,原因相同。这可以通过表约束完成。两个表级限制是UNIQUE和PRIMARY KEY,它们都是单列和多列。
UNIQUE约束表示列中的列或组合在表中是唯一的。但是如果在一个或多个列中有NULL,我们将允许它像一个唯一的值一样。 PRIMARY KEY声明与其中的所有列的NOT NULL和UNIQUE具有相同的效果。但是由于历史原因,一个表只能有一个PRIMARY KEY声明。这些列用作表之间的其他约束的默认值,但不要担心现在。
如何使用唯一性约束取决于所涉及的表的类型。一般来说,我们可以将表格分为三种:
1.实体
2.关系
3.辅助
实体表是由列建模的属性定义的同一类的一组事物。每行都是这种事情的一个实例。每行都有相同的列。如果你能看到它的感觉,看到或触摸它,那么它是一个实体。实体表的名称不应该是单数的(除非真的只有这个集合的一个成员),因为它建立一个集合。这个名字需要是复数形式,如果可能的话,需要集体。例如“员工”不好,“员工”较好,“人事”最好。
“树”不好,“树”更好,“森林”最好。你可以添加自己的例子。
实体也被分类为弱或强。一个强大的实体存在着自己的优点,而一个弱实体存在,因为一个或多个强大的实体。您需要购买才能享受折扣。
关系表引用一个或多个实体并建立它们之间的关系。除了引用实体之外,关系还可以具有自己的属性。婚姻执照号码属于婚姻,而不是丈夫,妻子或部长。
关系的程度是关系中实体的数量。二元关系有两个实体,我们在现实世界中喜欢它们,因为它们很简单。递归的二进制关系将实体与自身联系起来。一般的n-ary关系涉及n个实体,比如有买家、卖家和贷款人的房屋抵押贷款。将n-ary关系分解成二进制关系并不总是可行的。关系中的成员可以是可选的,也可以是强制性的。可选的会员资格意味着我们可以拥有一种零实体——一种购买并不总能得到折扣。
关系的基数是两个实体中每个实体的相关事件的实际数量。关系的基本类型是:一对一、一对多和多对多。这些术语通常具有可选的(0或更多)或强制性(1或更多)成员资格。
一对一(1:1)的关系是,当一个实体的一个实例与实体b的一个实例相关联时,例如,取一个传统的丈夫和妻子之间的关系。每个丈夫都有一个,只有一个妻子;每个妻子都有一个,只有一个丈夫。在本例中都是强制性的。
一对多(1:n)关系是指实体A的一个实例,它有零,一个或多个实体B的实例,但是对于实体B的一个实例来说,只有一个实体A的实例,一个例子可能是一个部门有很多雇员;每个员工都被分配到一个部门。根据您的业务规则,您可能允许一个未分配的雇员或一个空的部门。
一个多对多(m:n)关系,有时被称为非特定的,是当实体A的一个实例,有零个,一个或多个实体B的实例,一个实体B的实例有零个,一个,或多个实体A的实例,一个例子可能是披萨和客户。
辅助表既不是实体也不是关系;它提供信息。它们类似于日历或其他查找SQL中计算的表。他们经常被误解,被当作实体或关系表对待。
让我们把它变得更具体。销售订单是客户(实体)和库存(实体)之间的关系。订单细节是存在的一个弱实体,因为我们有一个订单。该关系有一个订单号,而不是库存或客户的一部分。运输成本是从一个辅助表中获得的。下面是这个示例的一些框架表。我正在使用GTIN(全球贸易项目编号)为客户提供订单项和DUNS(数据通用编号系统)。在设计数据库时,总是要寻找行业标准。
我们可以看到,销售订单是客户和库存之间的关系。订单有他们自己的密钥(ordernbr),但是没有任何东西可以强迫我们只使用有效的客户DUNS号码或产品GTIN代码来满足我们在库存中实际拥有的东西。事实上,我可以在命令表中插入明显无效的DUNS和GTIN代码,就像现在声明的那样。
这就是引用子句的用武之地。它使我们能够从数据模型中执行所有的基数和程度的东西。引用不是链接,也不是指针。这些是物理概念,参考是一个逻辑概念,我们不知道它是如何实现的。它所强制执行的规则是引用表列与引用表中的一行匹配。这意味着引用表中的行必须是惟一的;默认情况下,引用表中的主键是目标,但它不必是。引用表中的值被称为外键—它们不是表中的键,而是模式中的其他位置。
下面是骨架模式,上面有更多的肉:
注意,我们只需要在DUNS和GTIN是键的地方设置CHECK()约束,而不是在引用表中出现的地方。引用实体表、客户和库存;关系表、订单、引用其他表。这是一个一般的模式,但不是具体的。
此子句的多个列形式如下:
外键子句中的列在引用表中,必须匹配列的引用键,但可以有不同的名称。我可以通过在正确的位置放置唯一性约束来得到1:1、1、n和n:m的关系。作为一个腋窝表的例子,我们可以根据订单的总价值计算运费。表格可以是这样的:
虽然我们已经声明了辅助运输成本表的主键,但它不像实体的键——没有验证或验证,它不是标识符。要使用此表,我们将查询类似的问题:
作为一个练习,试着写一个约束,以防止开始和结束范围从重叠和有差距。如果需要,可以重新设计表。
在修改后的骨架模式中,当您试图为一个没有库存的产品执行订单时,您将会得到一个错误,它实际上是说,“它已经脱销了!”你可以试试别的办法。但如果你尝试删除一个产品库存,您还将得到一个错误实际上说,“嘿,有人命令这个垃圾”,所以你必须去每个订单和用别的东西代替的项目或NULL(如果允许)之前您可以删除它从库存。
这是使用声明引用完整性(DRI)操作的地方。的语法是:
删除和更新被称为“数据基事件”;当它们发生在表上时,会发生DRI操作。
1、NO ACTION=回滚事务您将得到一条消息。当您只有一个简单的引用子句时,这就是默认值。
2.SET DEFAULT = 被引用的列(s)被事件改变,但是引用的列被更改为它们的默认值。显然,引用列需要声明默认值。这些默认值必须在引用表中。SET NULL = 被引用的列(s)被事件改变,但是引用的列值被更改为null。显然,引用的列需要是允许空值的。这就是设置NULL的“可能存在的好处”所在。
3.CASCADE = 被引用的列(s)被事件改变,这些相同的值被级联到引用的列上。这是实践中最重要的选择。例如,如果我们想要停止一个项目,我们可以从库存中删除它,并且由于ON DELETE CASCADE,SQL引擎将自动删除表Sales_Order_Details(销售订单)中的匹配行。同样地,如果您在库存中更新一个项目,那么ON UPDATE CASCADE将自动在它引用的地方替换旧的值。
在执行了这些操作之后,引用完整性约束仍然有效。这是最后的骨架:
看看你能不能搞清楚:
- 一个客户死了,我们把他删掉了。
- 我们把草坪侏儒雕像改成了更有品味的粉红色火烈鸟。
- 我们不再使用粉红色的火烈鸟。
- 有人试图从第1步跳到第3步后命令一个草坪小精灵
很明显,我排除了补充库存的问题和一些其他问题,但我们后面会讲到这些。
本文是数据库设计阶梯的一部分。
引自:Joe Celko,2013/09/18(第一次出版:2010/05/25)