数据库设计第3级的阶梯:构建表

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据库设计第3级的阶梯:构建表相关的知识,希望对你有一定的参考价值。

By Joe Celko,2013/09/18(第一次出版:2010/05/25)

该系列

这篇文章是楼梯系列的一部分:楼梯到数据库设计

设计和创建数据库的新任务?Joe Celko是所有关于SQL的最广泛阅读的作家之一,他解释了这些基础知识。像往常一样,他偶尔会给最老练的数据库专业人士带来惊喜。乔连续四年成为DBMS杂志读者选择奖的获得者。他曾在美国、英国、北欧国家、南美和非洲教过SQL。他在ANSI /ISO SQL标准委员会任职10年,并为SQL - 89和SQL - 92标准做出了贡献。

                有几种类型的表,每个表都有对规则和完整性约束的特殊要求。无论需求如何,表级约束将确保规则被强制运行,数据完整性得到维护。

有几种类型的表,每个表都有对规则和完整性约束的特殊要求。在一级,我们将数据元素命名为它们并将它们分类。在第二级中,我们使用SQL中的数据类型和约束对数据元素进行建模,以提供行。在第3级,我们将把这些行放到表中。表不仅仅是一组以一个名字组合在一起的行。

列只能在表中出现一次。只有有意义;如果你记录了别人的鞋码两次,那么最好的情况是多余的,而在最坏的情况下,当列不一致时,就会显得模棱两可。现在我们可以在每行的列中有表级检查约束。它们与我们之前的一列检查约束没有太大的不同。它们可以被命名,并将出现在CREATE TABLE语句的列声明列表中,而不是附加到任何一行。例如:

CONSTRAINT Valid_Employee_Age-- don‘t hire people before they are born

 CHECK (emp_birth_date < emp_hire_date)

不将约束合并到一个巨大的CHECK()子句中通常是个好主意。错误消息将包含约束名称,因此单独的约束将使您更好地了解错误,而不是一个名为“bad_things_发生”约束的单个怪物。

继续我们对冗余的厌恶,在表一级,我们希望每一行都是独一无二的,原因是相同的。这可以通过表约束来完成。两个表级别的约束是唯一的和主键,它们同时出现在单个和多个列版本中。

唯一的约束表示列或列的组合在表中是唯一的。但是如果在一个或多个列中有一个空值,我们就会允许它作为一个惟一值。主键声明的效果与非空值相同,对于它的所有列都是唯一的。但是出于历史原因,一个表只能有一个主键声明。这些列用作表之间其他约束的默认值,但现在不要担心。

如何使用唯一性约束取决于所涉及的表的类型。一般来说,我们可以把一张桌子分成三种:

1.实体

2.关系

3.辅助

实体表是由列所建模的属性定义的同一类事物的集合。每一行都是这类事情的一个实例。每一行都有相同的列。如果你能看到它的感觉,看到或触摸它,那么它就是一个实体。实体表的名称不应该是单一的(除非这个集合中只有一个成员),因为它建模了一个集合。这个名称需要是复数,或者,如果可能的话,是集合。例如,“员工”是不好的,“员工”更好,“员工”是最好的。“树”不好,“树”更好,“森林”是最好的。您可以添加自己的示例。

实体也被分为弱或强。一个强大的实体以其自身的价值存在,而一个弱的实体存在是因为一个或多个强大的实体。你需要先买一件,然后再打折。

关系表引用一个或多个实体并建立它们之间的关系。关系可以有它自己的属性,除了对实体的引用。结婚证号码属于婚姻,不属于丈夫、妻子或牧师。

关系的程度是关系中实体的数量。二元关系有两个实体,我们喜欢它们在现实世界中,因为它们很简单。递归二元关系将实体与自身联系起来。一般的n - ary关系涉及n个实体,例如与买方、卖方和贷方的房屋抵押。将n - ary关系分解成二元关系并不总是可能的。关系中的成员资格可以是可选的,也可以是强制性的。可选会员意味着我们可以拥有一种零实体——一种购买并不总能得到折扣。

关系的基数是两个实体的实际相关事件的实际数量。关系的基本类型是:一对一、一对多和多对多。这些术语通常符合可选(0或以上)或强制性(1或以上)会员资格。

一对一(1:1)关系指的是实体A的一个实例与实体b的一个实例相关联,例如,以传统夫妻的关系为例。每个丈夫只有一个妻子;每个妻子只有一个丈夫。在这个例子中,两者都是强制性的。

一对多(1:n)关系是实体A的一个实例,其中有一个或多个实体B的实例,但对于实体B的一个实例,只有一个实体A的实例。一个例子可能是一个部门有许多员工;每个员工被分配到一个部门。根据您的业务规则,您可能允许未分配的员工或空部门。

一个多对多(m:n)关系,有时被称为非特定关系,是指实体A的一个实例,即实体B的一个或多个实例,一个实体B的实例为零、一个或多个实体的实例。一个例子可能是批萨和客户。

辅助表既不是实体,也不是关系;它提供信息。它们类似于日历或其他查找表,以SQL代替计算。他们经常被误解,被视为实体或关系表。

让我们更具体一点。一个销售订单是一个客户(实体)和我们的库存(实体)之间的关系。订单细节是存在的一个弱实体,因为我们有一个订单。关系有一个订单号,它不是库存或客户的一部分。运费由一个辅助表获得。下面是这个例子的一些框架表。我正在使用GTIN(全球贸易项目编号)为客户订购项目和DUNS(数据通用编号系统)。在设计数据库时,一定要注意行业标准。

CREATE TABLE Sales_Orders

(order_nbr INTEGER NOT NULL PRIMARY KEY

 CHECK (order_nbr > 0),

 customer_duns CHAR(9) NOT NULL,

 order_shipping_amt DECIMAL (5,2) NOT NULL

 CHECK (shipping_amt >= 0.00),

 etc);

 

CREATE TABLE Sales_Order_Details

(order_nbr INTEGER NOT NULL,

 gtin CHAR(15) NOT NULL,

 PRIMARY KEY (order_nbr, gtin),

 item_qty INTEGER NOT NULL

 CHECK (item_qty > 0),

 item_unit_price DECIMAL (8,2) NOT NULL

 CHECK (item_unit_price >=0.00));

 

CREATE TABLE Customers

(customer_duns CHAR(9) NOT NULL PRIMARY KEY

 CHECK (customer_duns LIKE ‘[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]‘),

 etc);

 

CREATE TABLE Inventory

(gtin CHAR(15) NOT NULL PRIMARY KEY

 CHECK (gtin LIKE ‘[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]‘),

 onhand_qty INTEGER NOT NULL

 CHECK (onhand_qty >= 0),

我们可以看到,销售订单是客户与库存之间的关系。订单有他们自己的关键(order_nbr),但是没有什么可以强迫我们只使用有效的客户DUNS号或产品GTIN代码来做我们实际有库存的东西。事实上,我可以将明显无效的DUNS和GTIN代码插入到Orders表中。

这就是引用子句的由来。它使我们能够从数据模型中强制执行所有的基数和度数据。引用不是链接或指针。这些都是物理概念,引用是一个逻辑概念,我们不知道它是如何实现的。它的作用是一个规则,引用表列与引用表中的单个行匹配。这意味着引用表中的行必须是唯一的;在默认情况下,引用表中的主键是目标,但不必如此。引用表中的值称为外键——它们不是表中的键,而是模式中的其他位置。

这是骨架图式,上面有更多的肉:

CREATE TABLE Sales_Orders

 

(order_nbr INTEGER NOT NULL PRIMARY KEY

 CHECK (order_nbr > 0),

 customer_duns CHAR(9) NOT NULL

 REFERENCES Customers(customer_duns),

 order_shipping_amt DECIMAL (5,2) DEFAULT 0.00 NOT NULL

 CHECK (shipping_amt >= 0.00),

 etc);

 

CREATE TABLE Sales_Order_Details

(order_nbr INTEGER NOT NULL

 REFERENCES Orders(order_nbr),

 gtin CHAR(15) NOT NULL

 REFERENCES Inventory(gtin),

 PRIMARY KEY (order_nbr, gtin),-- two column key

 item_qty INTEGER NOT NULL

 CHECK (item_qty > 0),

 item_unit_price DECIMAL (8,2) NOT NULL

 CHECK (item_unit_price >= 0.00));

 

CREATE TABLE Customers

(customer_duns CHAR(9) NOT NULL PRIMARY KEY

 CHECK (customer_duns LIKE ‘[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]‘),

 etc);

 

CREATE TABLE Inventory

(gtin CHAR(15) NOT NULL PRIMARY KEY

 CHECK (gtin LIKE ‘[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]‘),

 onhand_qty INTEGER NOT NULL

 CHECK (onhand_qty >= 0),

 etc);

注意,我们只需要在DUNS和GTIN是键的地方设置CHECK()约束,而不是在引用表中出现的地方。引用实体表、客户和库存;关系表、订单、引用其他表。这是一个一般的模式,但不是具体的。

此子句的多个列形式如下:

FOREIGN KEY (order_nbr, gtin)

 

REFERENCES Sales_Order_Details(order_nbr, gtin)

外键子句中的列在引用表中,必须匹配列的引用键,但可以有不同的名称。我可以通过在正确的位置放置唯一性约束来得到1:1、1、n和n:m的关系。作为一个腋窝表的例子,我们可以根据订单的总价值计算运费。表格可以是这样的:

CREATE TABLE Shipping_Costs

(start_order_amt_tot DECIMAL (10,2) NOT NULL,

 end_order_amt_tot DECIMAL (10,2) NOT NULL,

CONSTRAINT Valid_Shipping_Range

 CHECK (start_order_amt_tot < end_order_amt_tot),

PRIMARY KEY (start_order_amt_tot, end_order_amt_tot),

 shipping_amt DECIMAL (5,2) NOT NULL

 CHECK (shipping_amt > 0.00));

虽然我们已经声明了辅助运输成本表的主键,但它不像实体的键——没有验证或验证,它不是标识符。要使用此表,我们将查询类似的问题:

SELECT shipping_amt

  FROM Shipping_Costs

 WHERE <order amount total> BETWEEN start_order_amt_tot AND end_order_amt_tot;

作为一个练习,试着写一个约束,以防止开始和结束范围从重叠和有差距。如果需要,可以重新设计表。

在修改后的骨架模式中,当您试图为一个没有库存的产品执行订单时,您将会得到一个错误,它实际上是说,“它已经脱销了!”你可以试试别的办法。但如果你尝试删除一个产品库存,您还将得到一个错误实际上说,“嘿,有人命令这个垃圾”,所以你必须去每个订单和用别的东西代替的项目或NULL(如果允许)之前您可以删除它从库存。

这是使用声明引用完整性(DRI)操作的地方。语法是:

ON DELETE [NO ACTION | SET DEFAULT | SET NULL | CASCADE]

ON UPDATE [NO ACTION | SET DEFAULT | SET NULL | CASCADE]

删除和更新被称为“数据基事件”;当它们发生在表上时,会发生DRI操作。

1.不采取行动=回滚事务,您将得到一条消息。当您只有一个简单的引用子句时,这就是默认值。

2.SET DEFAULT =引用的列由事件改变,但是引用列被更改为默认值。显然,引用列需要在它们上声明默认值。这些默认值必须在引用表中。

3.SET NULL =引用的列被事件更改,但是引用列被更改为NULLs。显然,引用列需要为空。这就是“怀疑的好处”的原因。

4.级联=被引用的列被事件改变,相同的值被级联到引用列。这是实践中最重要的选择。例如,如果我们想要停止一个产品,我们可以从库存中删除它,而在delete CASCADE中,SQL引擎会自动删除Sales_Order_Details中的匹配行。同样,如果您在库存中更新项目,则更新级联将在引用的任何地方将旧值替换为新值。

在执行这些操作之后,引用完整性约束仍然有效。这是最后的骨架:

CREATE TABLE Sales_Orders

 

(order_nbr INTEGER NOT NULL PRIMARY KEY

 CHECK (order_nbr > 0),

 customer_duns CHAR(9) NOT NULL

 REFERENCES Customers(customer_duns)

 ON UPDATE CASCADE

 ON DELETE CASCADE,

 order_shipping_amt DECIMAL (5,2) DEFAULT 0.00 NOT NULL

 CHECK (shipping_amt >= 0.00),

 etc);

 

CREATE TABLE Sales_Order_Details

(order_nbr INTEGER NOT NULL

 REFERENCES Orders(order_nbr)

 ON UPDATE CASCADE

 ON DELETE CASCADE,

 gtin CHAR(15) NOT NULL

 REFERENCES Inventory(gtin)

 ON UPDATE CASCADE

 ON DELETE CASCADE,

 PRIMARY KEY (order_nbr, gtin),-- two column key

 item_qty INTEGER NOT NULL

 CHECK (item_qty > 0),

 item_unit_price DECIMAL (8,2) NOT NULL

 CHECK (item_unit_price >= 0.00));

看看你是否能弄清楚发生了什么事:

1.一个客户去世了,我们把他删除了。

2.我们把草坪上的侏儒雕像换成了更有品位的火烈鸟。

3.我们停止了粉红色的火烈鸟。

4.1到3步,有人想要草坪小矮人

很明显,我不考虑重新进货的问题和其他事情,但我们将会得到这些。

 

 
 

以上是关于数据库设计第3级的阶梯:构建表的主要内容,如果未能解决你的问题,请参考以下文章

翻译:数据库设计层次3:构建表

[翻译]——SQL Server索引的介绍:SQL Server索引级的阶梯

第八周翻译

5级阶梯SQL Server索引

高级T-SQL第1级的阶梯:使用交叉连接来引入高级T-SQL

MySQL 对于千万级的大表要怎么优化