数据库设计
译自:https://www.datanamic.com/support/lt-dez005-introduction-db-modeling.html
本文将讲述关系型数据库设计的基础,并解释如何进行良好的数据库设计。文章很长,但我们建议你读完它。数据库设计是相当容易的,但有一些规则需要坚守。知其然更要知其所以然,否则,很容易犯错。
标准化使数据模型更加灵活,从而使处理数据更加容易。请花点时间去学习这些规则,并应用它们。本文使用的数据库是用数据库设 计和建模工具design for Databases设计的。
一个好的数据库设计,首先要列出,你想要保存的数据,以及对其进行的操作。尝试首先用人类语言进行描述,而不要去想 table, column 这些概念。要认真对待这个问题,否则很容易返工。数据库是软件开发中很重要的一部分,值得你多花些时间。
实体 Entities
保存在数据库中的信息,一般称为实体。实体主要包含四种类型:人 (people),物 (things),事件 (events),以及地点 (locations)。如果你发现某种信息不能归入以上四类,那他很可能不是实体,而是实体的属性 (property, attribute)。
让我们使用以下例子,以便于理解。想象,你要创建一个电商网站,都需要处理哪些信息呢?在一家商店 (shop),你做的事情,是出售商品 (products),给顾客 (customers)。
- 商店 shop,是一个 location.
- 出售 sale,是一个 event
- 商品 products,是 things
- 顾客 customers,是 people 以上就是所有需要数据库需要包含的全部的实体。
在交易过程中,还有别的事情发生吗?一个顾客走进商店,靠近一名售货员(vendor),问了一个问题,得到一个答案。售货员也参与了这个过程,并且是 people,所以我们需要一个售货员实体。
Figure 1: Entities: type of information.

关系 Relationships
下一步是确定实体之间的关系,以及每个关系的基数(Cardinality)。关系是实体间的联系,跟真实世界类似:一个实体会对另一个实体做什么?他们之间有什么关系?比如顾客 (customers) 购买 商品 (products),商品被卖给顾客,一次出售 (sale),包含若干商品,并发生在一家商店 (shop)
基数(cardinity) 表达的是关系两端实体间的数量对比。对于每一个关系,你都要先声明,左侧的一个实体,能包含多少个右侧的实体。比如:一次出售,能有多少顾客呢?一个顾客,能属于多少次出售活动?一个商店,能发生多少次出售?
你将会有如下结论: (请注意,商品 product 代表某一种类的商品,而不是某一个特定商品)
- Customers --> Sales; 一个顾客可以多次进行购买
- Sales --> Customers; 一次出售活动,只属于一名顾客
- Customers --> Products; 一个顾客可以购买多种商品
- Products --> Customers; 一个商品可以被多名顾客购买
- Customers --> Shops; 一个顾客可以到多个商店进行购买
- Shops --> Customers; 一个商店可以接待多名顾客
- Shops --> Products; 一个商店有多种商品
- Products --> Shops; 一类商品可以在多个商店进行售卖
- Shops --> Sales; 一个商店可以进行多次售卖
- Sales --> Shops; 一次售卖只能发生在一家商店
- Products --> Sales; 一类商品可以多次被出售
- Sales --> Products; 一次售卖可以包含多种商品
如何确定我们已 经包含了所有关系呢?共有四种实体,每种实体与其他三种实体之间,都有关系。所以共有 4*3 = 12 种关系。
接下来让我们进行汇总,来找到整体关系的基数。为了实现这个目的,我们首先列出每一种关系的基数。为了使这个过程变得简单,我们会对箭头进行调整,改变其方向:
- Customers --> Sales; 一个顾客可以多次进行购买
- Sales --> Customers; 一次出售活动,只属于一名顾客
我们会改变第二个关系的方向,让实体顺序保持一致。
- Customers <-- Sales; 一次出售活动,只属于一名顾客
基数有四种类型:一对一,一对多,多对一,多对多。在数据库设计中,这表示为:1:1,1:N, M:1,和 M:N。
- Customers --> Sales; 一个顾客可以多次进行购买;1:N
- Customers <-- Sales; 一次出售活动,只属于一名顾客;1:1
最终的基数,便是左右两侧分别分配最大数,其中,N 和 M 都大于 1。上述关系中,两种情况左侧都是 1,右侧则分别为 1 和 N,N 是最大值。所以基数为 1:N。一个顾客可以进行多次购买,但是一次购买只属于一名顾客。
让我们为其他关系也进行类似计算,得到:
- Customers --> Sales; --> 1:N
- Customers --> Products; --> M:N
- Customers --> Shops; --> M:N
- Sales --> Products; --> M:N
- Shops --> Sales; --> 1:N
- Shops --> Products; --> M:N
最终,我们将会有两个 一对多 关系,以及四个 多对多 关系。
Figure 2: Relationships between the entities.

实体之间可能会有互相依赖。这意味着,如果某一个条目不存在,那么令一个条目也不可能存在。比如,如果没有顾客,那么便不可能有销售;如果没有商品,也不会有销售。
销售 ---> 顾客,销售 ---> 产品 这两种关系是强制依赖,反过来则不然。即使没有销售,顾客、产品也可以存在。这对接下来的内容很重要。
递归关系 Recursive Relationships
有时候,一个实体会拥有指向自己的关系。比如,一名 empolyee 拥有一个 boss,而 boss 本身也是一名 employee。employees 实体的 boss 属性(attribute) 指向 employees 实体
在一个 ERD 中,这种关系表现为从实体中出发的线条,返回到当前实体。
冗余关系 Redundant Relationships
有时候你的数据模型中存在冗余关系。这是一种已经在其他关系中间接包含的关系。
在我们的例子中,顾客和产品有直接关系。但是 顾客与销售、销售与产品之间,也有关系。其中便包含了顾客与产品之间通过销售的间接关系。Customers <----> Products 关系出现了两次,其中之一就是冗余关系。在这个例子中,产品只会通过销 售被购买所以 Customers <----> Products 的直接关系应该被删除。之后,数据模型如下:
Figure 3: Relationships between the entities.

处理多对多关系
在数据库中,我们没法直接实现多对多 (M:N) 关系。 多对多关系意味着,一张表中的若干条记录,属于另一张表中的若干条记录。需要另寻他处来存储这些关系,一般解决方案是将其分成两组一对多的关系。
可以通过在两组实体间,创建一张新表来实现。在我们的例子中,销售与产品间是多对多关系,我们为此创建一个新实体:sales-products。这个实体同时与销售、产品具有多对一的关系。从逻辑模型层面,这被称为关联实体(associative entity);而在物理数据库术语中,这被称为链接表、交集表或联结表(link table, intersection table or junction table)。
Figure 4: Many to many relationship implementation via associative entity.
除了 Products <----> Sales,另一个需要解决的多对多关系是 Products <----> Shops。这两种情况,我们都需要创建新实体,但这个新实体应该如何命名呢?
对于 Products <----> Sales 关系,每次销售包含多种产品。这个关系展示了销售的内容或者说销售的细节。所以可以称之为 销售细节(Sales details),你也可以称之为 卖掉的产品(sold products)。
Products <----> Shops 关系则展示了商店中可以购买的产品,也就是库存(sock)。
我们的数据模型如下:
Figure 5: Model with link tables Stock and Sales_details.

属性 Attributes
每个实体中,我们想保存的数据,便是其属性。 对于产品,我们两了解的是价格、制造商、型号。对于顾客,是顾客编号、姓名、地址。对于销售,是什么时候发生、在哪个商店,包含哪些产品,以及总价。对于服务员,是员工号码、姓名、地址。具体的内容现在不重要,只是要列出你想保存的条目。
Figure 6: Entities with attributes.
