数据库设计(3/9):创建表

对于设计和创建数据库完全是个新手?没关系,Joe Celko, 世界上读者数量最多的SQL作者之一,会告诉你这些基础。和往常一样,即使是最专业的数据库老手,也会给他们带来惊喜。Joe是DMBS杂志是多年来最受 读者喜爱的作者。他在美国、英国,北欧,南美及非洲传授SQL知识。他在ANSI / ISO SQL标准委员会工作了10年,为SQL-89和SQL-92标准做出了杰出贡献。


有很多表类型,每个都有它们特定规则和完整性约束的需求。不管什么需求,表层级的约束会确保那些规则被执行,数据完整性被保持。

在第一篇,我们为了它们是什么,更好的区分它们来命名数据元。在第二篇,我们用SQL里的数据类型和约束来模型化数据元,来提供我们行。在第三篇,我们将把这些行放入表。表是在一个名称下,很多行的集合。

在表里,列只会出现一次。这样做是有道理的;如果你两次记录某人的鞋子大小,这将是多余的,当列不一致时是混淆的。现在我们可以有表层级的在每行的列里的检查(CHECK)约束。这和之前列上的(CHECK)检查并没有啥区别。它们可以在CREATE TABLE语句里,多个列声明里命名并出现,不附加到任何行。例如:

1 CONSTRAINT Valid_Employee_Age-- don‘t hire people before they are born
2  CHECK (emp_birth_date < emp_hire_date) 

通常不应该把检查组合成一个大的CHECK()子句。错误信息会包含约束名称,因此独立的约束会,相比单个复杂命名的约束,给让你更清楚的发现问题。

继续我们的冗余问题,在表层级我们想每个行因同个原因而唯一。这可以通过约束实现。两个表层级的约束是UNIQUE和PRIMARY KEY,它们可以是单列或多列组合。

UNIQUE约束表示在表里,列或列的组合是唯一的。但在列或多个列中有NULL,如果它是唯一值,我们还是允许的。PRIMARY KEY声明,与对于表里面的所有列,NOT NULL且UNIQUE有同样的效果。但由于历史原因,表只能有一个PRIMARY KEY声明。这些列用来作为表之间的其他约束,但现在不要担心这个。

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

  1. 实体(Entity)
  2. 关系(Relationship)
  3. 辅助(Auxiliary)

实体表是多个同类事物,通过列的模型属性定义。每一行是这类东西的实例。每行有同样的列。如果你可以看到它感觉,看到它或感受它,那它是一个实体。实体表的命名不应该是单数(除非这个集合里真的只有一个成员),因为它模型化了一组。命名应该是复数,可能的话,使用集合命名。例如,“Employee”不好,“Employees”更好,“Personnel”最好。“Tree”不好,“Trees”更好,“Forest”最好。你可以添加你自己的例子。

实体也区分弱和强。强实体存在有它自己的优点,同时,弱实体存在因为一个或多个强实体。你需要购买前,你可以有个折扣。

关系表指的是一个或多个实体表,并且它们之间建立关系。关系可以有它自己委外引用实体的属性。结婚登记号属于婚姻,不属于丈夫,妻子或牧师。

关系级别是关系里实体的个数。二元关系有2个实体,在现实世界中我们喜欢它们,因为它们简单。二元迭代关系关联到实体本身。一般的n元关系涉及n个实体,就像有买家,卖家和银行的房贷。通常不能把n元关系分解为二元关系。成员的关系可以是可选或必须的。可选的关系表示我们可以有一类的0实体——并不是所有的买卖都有折扣。

关系基数是对于每2个实体,相关出现的实际数量。关系的基本连接类型有:1:1,1:n,和n:n。这些术语通常是符合可选(0或更多)或必须的(1或更多)的关系。

1:1关系是一个实体A的最多一个实例与实体B的一个实例关联的时候。例如,拿通常的丈夫和妻子的关系。每个丈夫有且只要一个妻子;每个妻子有且只有一个丈夫。在这个例子都是必须一个的。

1:n关系是实体A的一个实例,对于实体B的一个实例有0个,一个或多个实体B的实例,实体A的实例只有一个的时候。一个例子会是一个部门有很多员工;每个员工分配到一个部门。取决于你的业务规则,你会允许未分配部门的员工或空的部门。

n:n关系,有时称作非特定的,对于实体A的一个实例,有0个,一个或多个实体B的实例,并且对于实体B的一个实例,有0个,一个或多个实体A的实例。这样的例子可以是披萨和客户。

辅助表不是实体也不是关系;它提供信息。它们是像日历或在SQL里替换计算的查询表(look up tables)。它们经常被误解被当实体或关系表对待。

我们来具体说下。销售订单是客户(实体)和我们的库存(实体)之间的关系。订单明细是存在的弱实体,因为我们有订单。这个关系有一个不是库存或客户一部分的订单号。运费从辅助表获得。对于这个例子,这里我用了一些骨架表。对于订单项目,我使用GTIN(Global Trade Item Number),对于客户,我使用GUNS(Data Universal Numbering System)。在你设计数据库的时候,记得都先看看行业标准。

 1 CREATE TABLE Sales_Orders
 2
 3 (order_nbr INTEGER NOT NULL PRIMARY KEY
 4
 5  CHECK (order_nbr > 0),
 6
 7  customer_duns CHAR(9) NOT NULL,
 8
 9  order_shipping_amt DECIMAL (5,2) NOT NULL
10
11  CHECK (shipping_amt >= 0.00),
12
13  etc);
14
15 CREATE TABLE Sales_Order_Details
16
17 (order_nbr INTEGER NOT NULL,
18
19  gtin CHAR(15) NOT NULL,
20
21  PRIMARY KEY (order_nbr, gtin),
22
23  item_qty INTEGER NOT NULL
24
25  CHECK (item_qty > 0),
26
27  item_unit_price DECIMAL (8,2) NOT NULL
28
29  CHECK (item_unit_price >=0.00));
30
31 CREATE TABLE Customers
32
33 (customer_duns CHAR(9) NOT NULL PRIMARY KEY
34
35  CHECK (customer_duns LIKE ‘[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]‘),
36
37  etc);
38
39 CREATE TABLE Inventory
40
41 (gtin CHAR(15) NOT NULL PRIMARY KEY
42
43  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]‘),
44
45  onhand_qty INTEGER NOT NULL
46
47  CHECK (onhand_qty >= 0),

我们可以看到订单表是客户和库存间的关系。订单有它们自己的主键(order_nbr),但没有东西强制我们使用有效的客户DUNS号或对于我们库存里的产品GTIN号。事实上,我可以插入显然无效的DUNS和GTIN码到订单表,现在就这样声明。

这就是我们要引入REFERENCES子句的地方。它是让我们从数据模型强制所有基数和程度的东西。引用(reference)不是个链接或指针。这些是物理概念,引用是个逻辑概念,我们不知道它如何实现。它强制的是,在引用表里,引用表列符合单行的规则。这意味着在引用表里的行必须唯一;默认情况下,在引用表里可以使用主键(PRIMARY KEY),但不必这样。在引用表的值可以称为外键(Foreign Keys)——它们不在它们的表里,但在架构里的其它地方。

这是上面有更多信息的主要架构:

 1 CREATE TABLE Sales_Orders
 2 (order_nbr INTEGER NOT NULL PRIMARY KEY
 3  CHECK (order_nbr > 0),
 4  customer_duns CHAR(9) NOT NULL
 5  REFERENCES Customers(customer_duns),
 6  order_shipping_amt DECIMAL (5,2) DEFAULT 0.00 NOT NULL
 7  CHECK (shipping_amt >= 0.00),
 8  etc);
 9
10 CREATE TABLE Sales_Order_Details
11 (order_nbr INTEGER NOT NULL
12  REFERENCES Orders(order_nbr),
13  gtin CHAR(15) NOT NULL
14  REFERENCES Inventory(gtin),
15  PRIMARY KEY (order_nbr, gtin),-- two column key
16  item_qty INTEGER NOT NULL
17  CHECK (item_qty > 0),
18  item_unit_price DECIMAL (8,2) NOT NULL
19  CHECK (item_unit_price >= 0.00));
20
21 CREATE TABLE Customers
22 (customer_duns CHAR(9) NOT NULL PRIMARY KEY
23  CHECK (customer_duns LIKE ‘[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]‘),
24  etc);
25
26 CREATE TABLE Inventory
27 (gtin CHAR(15) NOT NULL PRIMARY KEY
28  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]‘),
29  onhand_qty INTEGER NOT NULL
30  CHECK (onhand_qty >= 0),
31  etc); 

注意,在DUNS和GTIN是主键的地方,我们只有CHECK()约束,不是在它们出现的引用表里。实体表,客户和库存是引用的;关系表,订单,引用了其它表。这是常用模式,但这个设置不是固定的。

这个子句的多列看起来像这样:

1 FOREIGN KEY (order_nbr, gtin)
2 REFERENCES Sales_Order_Details(order_nbr, gtin) 

在FOREIGN KEY子句的列是引用表里需要匹配的引用主键,列对列,但会有不同的名称。我可以通过在相应的地方放置唯一约束来设置1:1,1:n和n:n的关系。作为辅助表的一个例子,我们可以基于订单总额计算运费。表看起来像这样:

1 CREATE TABLE Shipping_Costs
2 (start_order_amt_tot DECIMAL (10,2) NOT NULL,
3  end_order_amt_tot DECIMAL (10,2) NOT NULL,
4 CONSTRAINT Valid_Shipping_Range
5  CHECK (start_order_amt_tot < end_order_amt_tot),
6 PRIMARY KEY (start_order_amt_tot, end_order_amt_tot),
7  shipping_amt DECIMAL (5,2) NOT NULL
8  CHECK (shipping_amt > 0.00));

当我们在辅助运费表上声明了主键(PRIMARY KEY),对于实体,它不想主键——没有验证或核查,它不是个标识。使用这个表,我们可以这样查询:

1 SELECT shipping_amt
2   FROM Shipping_Costs
3  WHERE <order amount total> BETWEEN start_order_amt_tot AND end_order_amt_tot;

作为练习,尝试写下会从重复和断层上阻止开始和结束范围的约束。如果你需要的话,可以重新设计表。

在修正后的主要架构里,当你下没有库存的订单,你会收到错误提示“没有库存!”,这样的话,你可以试下别的。但如果你尝试从库存里删除产品,你同样也会收到错误提示“额,有人已经下了此产品的订单”,因此在可以从库存里删它之前,你必须到每个订单用别的值或NULL值来替换它。

这里就是引用完整性(Declarative Referential Integrity (DRI))用的地方。语法是:

1 ON DELETE [NO ACTION | SET DEFAULT | SET NULL | CASCADE]
2 ON UPDATE [NO ACTION | SET DEFAULT | SET NULL | CASCADE]

删除和更新是所谓的“数据基础事件(data base events)”;当它们发生到表时,就会发生DRI操作。

  1. NO ACTION:事务回滚,你收到提示。当有简单的REFERENCES子句时的默认操作。
  2. SET DEFAULT:引用的列通过事件改变,但引用的列值会修改为它们的默认值。当然,引用的列在它们上面要有声明的默认值。这些默认需要在引用表里。
  3. SET NULL:引用的列通过事件改变,但引用的列值会修改为NULL。当然,引用的列允许NULL值。这是引入NULL值“无罪推定(benefit of the doubt)”的地方。
  4. CASCADE:引用的列通过事件改变,这些值会级联到引用的列。实际中这是最重要的选项。例如,如果我们想停止一个产品,我们可以从库存里删除它,ON DELETE CASCADE会让SQL引擎会在Sales_Order_Details自动删除匹配的行。同样,如果在库存里更新一个项目,ON UPDATE CASCADE会用新值自动替换引用的列。

在这些操作完成后,引用完整性约束还是有效的。这是最终的架构:

 1 CREATE TABLE Sales_Orders
 2 (order_nbr INTEGER NOT NULL PRIMARY KEY
 3  CHECK (order_nbr > 0),
 4  customer_duns CHAR(9) NOT NULL
 5  REFERENCES Customers(customer_duns)
 6  ON UPDATE CASCADE
 7  ON DELETE CASCADE,
 8  order_shipping_amt DECIMAL (5,2) DEFAULT 0.00 NOT NULL
 9  CHECK (shipping_amt >= 0.00),
10  etc);
11
12 CREATE TABLE Sales_Order_Details
13 (order_nbr INTEGER NOT NULL
14  REFERENCES Orders(order_nbr)
15  ON UPDATE CASCADE
16  ON DELETE CASCADE,
17  gtin CHAR(15) NOT NULL
18  REFERENCES Inventory(gtin)
19  ON UPDATE CASCADE
20  ON DELETE CASCADE,
21  PRIMARY KEY (order_nbr, gtin),-- two column key
22  item_qty INTEGER NOT NULL
23  CHECK (item_qty > 0),
24  item_unit_price DECIMAL (8,2) NOT NULL
25  CHECK (item_unit_price >= 0.00)); 

看看下面的情况发生时,你会找出会发生什么?

  1. 一个客户走了,我们删除它。
  2. 我们修改Lawn Gnome雕像为更有品味的Pink Flamingo。
  3. 我们停止销售Pink Flamingo。
  4. 在1-3步骤后,有人尝试下Lawn Gnome订单。

显然,我留下未处理的问题和其他东西,但我们会接触这些。

原文链接:

http://www.sqlservercentral.com/articles/Stairway+Series/69927/

时间: 2024-09-30 11:10:36

数据库设计(3/9):创建表的相关文章

Android数据库高手秘籍(二)——创建表和LitePal的基本用法

文章中我们学习了一些Android数据库相关的基础知识,和几个颇为有用的SQLite命令,都是直接在命令行操作的.但是我们都知道,数据库是 要和程序结合在一起使用的,单独对一个数据库去进行増删改查操作并没有什么意义,因此今天我们就来学习一下如何在Android程序当中去操作 SQLite数据库,还没看过前一篇文章的朋友可以先去参考 Android数据库高手秘籍(一)——SQLite命令 . 操作数据库的第一步当然是创建表了,传统创建表的方法相信大多数人都知道,那么今天我除了会展示传统的建表方法之

SQL语句创建数据库,SQL语句删除数据库,SQL语句创建表,SQL语句删除表,SQL语句添加约束,SQL语句删除约束

创建数据库: 1 CREATE DATABASE Test --要创建的数据库名称 2 ON PRIMARY 3 ( 4 --数据库文件的具体描述 5 NAME='Test_data', --主数据文件的逻辑名称 6 FILENAME='E:\project\Test_data.mdf', --主数据文件的物理名称 7 SIZE=5MB, --主数据文件的初始大小 8 MAXSIZE=100MB, --主数据文件的增长的最大值 9 FILEGROWTH=15% --主数据文件的增长率 10 )

数据库学习之初始-创建表及几个命令

所用软件:mysql server 一,创建一个表 用creat create table candidats(candidats_id int(10)   not null   auto_increment,candidats_name char(10) not null,candidats_addr char(50) null,candidats_age char(10) null,candidats_school char(50) null,candidats_company char(5

数据库--------用代码来创建表

一.首先创建一个数据库,名叫“ssss" 二.点击查询 三.点击,创建查询,会出现一个创建查询的窗口: 四.在查询编辑器,中写代码

数据库设计之area区域表改版后

原先版本:http://blog.csdn.net/u012012240/article/details/51221080 原先版本直辖市的省级别和市级别为1条记录,1条记录即代表省又代表市,操作起来很不方便! 这个版本创建了个直辖市记录为省级记录,将原先直辖市记录的parent改为省级直辖市的id,这样方便操作及理解! /* SQLyog v10.2 MySQL - 5.6.17 : Database - whiteshirt *********************************

Qt 数据库创建表失败原因之数据库关键字

本人数据库新手,在创建表时出现问题,最后经查证,找出问题所在.下面的程序是部分节选,在创建数据库表的时候,起先使用的L24的CreateDB,经测试,一直输出 Create testResult Fail, query->isActive()也为false,这就说明问题出在createDB上,也就是说这个QString应该是不符合要求才出错的.后来逐个测试,最后发现是 check varchar(100)的原因,经查看check是数据库关键字. 1 //创建数据库文件路径 2 testResul

mySQL教程 第1章 数据库设计

第1章 数据库设计 E-R设计 很多同学在学SQL语句时,觉得非常困难,那是因为你在学一个你根本不了解的数据库,数据库中的表不是你设计的,表与表之间的关系你不明白.因此在学SQL语句之前,先介绍一下数据库设计. 下面举例说明数据库设计: 学校需要开发一个系统记录有学生.课程和成绩信息.数据库如何设计? 这里面涉及到两个实体,学生表.课程,这些表为实体表. 这些表之间有什么关系呢?.学生考试出成绩,成绩记录在成绩表. 一个学生可以参加多门课程,关系是1对多. 数据库设计实例 设计数据库和表 安装m

分析论坛数据库设计&amp;分析

1,数据库设计 论坛数据库设计还是挺有意思的,按照业务逻辑进行拆分的数据库设计. 首先,如果是一个博客就一个post表记可以了.然后考虑到论坛数据量比较大,所以在设计上有优化. 论坛把数据库分成3个数据表,这样在访问不同页面的时候都查询很快. 数据库表参考discuz 数据库设计: 只是挑了些相关字段,没有把所有字段列出了. --论坛板块表 CREATE TABLE forum ( fid mediumint(8) unsigned NOT NULL AUTO_INCREMENT, `name`

MSSQL之六 数据库设计

本章主要讲解关系数据理论,以及数据库的设计过程 . 关系数据理论方面主要介绍对范式的理解,关系模式是关系所设计的属性的集合.这些属性的设置是出于对刻画实体以及实体间联系的需要.在保证能够满足这种需要的前提下这些属性是否都是必要的?如果不是,就应该将其删除,否则会造成数据冗余和其他的一些问题,而范式就是为了解决这些问题而产生的 数据库设计过程主要介绍在通常情况下,设计数据库要经过的步骤. 重点 ?  不同范式的联系和区别 ?  数据库设计过程的步骤 预习功课 ?        了解函数依赖的含义

数据库设计范式实例解析

设计范式(范式,数据库设计范式,数据库的设计范式)是符合某一种级别的关系模式的集合.构造数据库必须遵循一定的规则.在关系数据库中,这种规则就是范式.关系数据库中的关系必须满足一定的要求,即满足不同的范式.目前关系数据库有六种范式:第一范式(1NF).第二范式(2NF).第三范式(3NF).第四范式(4NF).第五范式(5NF)和第六范式(6NF).满足最低要求的范式是第一范式(1NF).在第一范式的基础上进一步满足更多要求的称为第二范式(2NF),其余范式以次类推.一般说来,数据库只需满足第三范