数据库设计 经验

http://office.microsoft.com/zh-cn/access-help/HP005189136.aspx

http://office.microsoft.com/zh-cn/access-help/HA001224247.aspx

什么是模式?

什么是模式?简单说来,模式类似于定式,就是遇到反复出现的同一问题时所固定使用的解决方案。下围棋的朋友可能对“定式”这个词比较熟悉,定式包含着下棋时做遇到的各种情况下的下法、急所、手筋及死活等基本原理,例如星定式、小目定式、边定式等等,定式懂的越多,围棋下的越好。

那么是不是数据库设计模式懂得越多,设计工作越完美呢?理论上是这样,但是在我这里,各位朋友所能看到的数据库设计模式只有四种。

为什么只有四种而不是更多?

不时有那句话吗:“浓缩的都是精华”!

在后面的文章中,您会陆续看到浩浩荡荡的设计实例连篇累牍,却都是利用这四种基本模式设计出来的。《易传·系辞》曰:“易有太极,是生两仪,两仪生四象,四象生八卦。”老子在《道德经》中也说:“道生一,一生二,二生三,三生万物。”

设计模式不必多,只要掌握其中关键的几个,再结合实际的业务需求,一个完整的数据库模型就可以推导出来。

下面让我们来逐一介绍这四种主要设计模式——

(一)主扩展模式

主扩展模式,通常用来将几个相似的对象的共有属性抽取出来,形成一个“公共属性表”;其余属性则分别形成“专有属性表”,且“公共属性表”与“专有属性表”都是“一对一”的关系。

“专有属性表”可以看作是对“公共属性表”的扩展,两者合在一起就是对一个特定对象的完整描述,故此得名“主扩展模式”。

举例如下(注:这个例子已经作了相当程度的简化,仅仅是用来帮助大家理解“主扩展模式”这个概念来使用的,请大家注意)。

假设某公司包括如下6种类型的工作人员:采购员、营销员、库房管理员、收银员、财务人员和咨询专家,采用主扩展模式进行设计,如下图所示。

图1

无论哪种类型的工作人员,都要访问公司的办公软件,所以都有“登陆代码”和“登录密码”;并且作为一般属性,“姓名”、“性别”、“身份证号”、“入职时间”、“离职时间”等属性,都与个人所从事的工作岗位无关,所以可以抽取出来作为公共属性,创建“公司员工”表。

很显然,公司委派员工采购哪些商品是“采购员”的专有属性,这是由公司的实际业务特点决定的。换句话说,公司不可能把采购任务放到“营销员”身上,也不可能放到“库房管理员”身上,“采购商品”属性就是“采购员”的专用属性。

“采购员”表的主键与“公司员工”表的主键是相同的,包括字段名称和字段的实际取值;“采购员”表的主键同时是“公司员工”表主键的外键。在PDM图里可以看到“采购员”表中的“员工ID”字段后面有一个“<pk,fk>”标记,这个标记就说明“员工ID”字段既是“采购员”表的主键,同时也是该表的外键。

“公司员工”表是主表,“采购员”表是扩展表,二者是“一对一”的关系,两个表的字段合起来就是对“采购员”这个对象的完整说明。同理,“公司员工”表和其他5个表之间也都分别构成了“一对一”的关系。

对于主表来说,从表既可以没有记录,也可以有唯一一条记录来对主表进行扩展说明,这就是“主扩展模式”。

(二)主从模式

主从模式,是数据库设计模式中最常见、也是大家日常设计工作中用的最多的一种模式,它描述了两个表之间的主从关系,是典型的“一对多”关系。

举例如下(注:这个例子已经作了相当程度的简化,仅仅是用来帮助大家理解“主从模式”这个概念来使用的,请大家注意)。

比如论坛程序。一个论坛通常都会有若干“板块”,在每个板块里面,大家可以发布很多的新帖。这时候“板块”和“发帖”就是主从模式,主表是“板块”,从表是“发帖”,二者是“一对多”的关系。

多个潜水员也可以对感兴趣的同一份发帖进行回复,以表达各自的意见,这时候,一个“发帖”就有了多份“回复”,又构成了一个“主从模式”。图2

(三)名值模式

名值模式,通常用来描述在系统设计阶段不能完全确定属性的对象,这些对象的属性在系统运行时会有很大的变更,或者是多个对象之间的属性存在很大的差异。

举例如下(注:这个例子已经作了相当程度的简化,仅仅是用来帮助大家理解“名值模式”这个概念来使用的,请大家注意)。

1. 使用名值模式进行设计时,如果对“其他属性”仅作浏览保存、不作其它任何特殊处理,则通常会设计一个“属性模板”表,该表的数据记录在系统运行时动态维护。

系统运行时,如需维护“产品其他属性”,可先从“属性模板”中选择一个属性名称,然后填写“属性值”保存,系统会将对应的产品ID、属性模板ID及刚刚填写的“属性值”一起保存在“产品其他属性”里,这样就完成了相关设置。无论产品的其他属性需求发生怎样的变化、怎样增删改属性,都可以在运行时实现,而不必修改数据库设计和程序代码。(见下图)

图3

2. 使用名值模式进行设计时,如果对“其他属性”有特殊处理,比如统计汇总,那么这个属性名称需要在程序代码中作“硬编码”,即该属性名称需要在程序代码中有所体现,此时可以在“产品其他属性”表中直接记录“属性名称”,不再需要“属性模板”表。

系统运行时,如需维护“产品其他属性”,程序直接列出“属性名称”,然后填写“属性值”保存,系统会将对应的产品ID、属性名称及刚刚填写的“属性值”一起保存在“产品其他属性”里,这样就完成了相关设置。以后如果需求发生变更,则只需修改相应的程序代码即可,不必修改数据库设计。(见下图)

图4

(四)多对多模式

多对多模式,也是比较常见的一种数据库设计模式,它所描述的两个对象不分主次、地位对等、互为一对多的关系。对于A表来说,一条记录对应着B表的多条记录,反过来对于B表来说,一条记录也对应着A表的多条记录,这种情况就是“多对多模式”。

“多对多模式”需要在A表和B表之间有一个关联表,这个关联表也是“多对多模式”的核心所在。根据关联表是否有独立的业务处理需求,可将其划分为两种细分情况。

1. 关联表有独立的业务处理需求。

举例如下(注:这个例子已经作了相当程度的简化,仅仅是用来帮助大家理解“多对多模式”这个概念来使用的,请大家注意)。

比如网上书店,通常都会有“书目信息”和“批发单”。一条“书目信息”面对不同的购买客户、可以存在多张“批发单”,反过来,一张“批发单”也可以批发多条书目,这就是多对多模式。中间的“批发单明细”表就是两者的关联表,具备独立的业务处理需求,是一个业务实体对象,因此它具备一些特有的属性,比如针对每一条明细记录而言的“累计退货次数”、“累计退货数量”、“累计结算次数”、“累计结算数量”;由于批发单明细在数据产生后已经打印出纸质清单提供给客户,因此在“批发单明细”表里对纸质清单中打印的书目信息属性作了冗余(逆标准化),这样在将来即使修改了“书目信息”表中的属性,也不会影响跟客户核对批发单明细,不会影响未来的财务结算业务。

图5

2. 关联表没有独立的业务处理需求

举例如下(注:这个例子已经作了相当程度的简化,仅仅是用来帮助大家理解“多对多模式”这个概念来使用的,请大家注意)。

比如用户与角色之间的关系,一般系统在做权限控制方面的程序时都会涉及到“系统用户表”和“系统角色表”。一个用户可以从属于多个角色,反过来一个角色里面也可以包含多个用户,两者也是典型的“多对多关系”。其中的关联表“用户角色关联表”在绝大多数情况下都是仅仅用作表示用户与角色之间的关联关系,本身不具备独立的业务处理需求,所以也就没有什么特殊的属性。

图6

(五)使用上述四种模式的一般原则
1. 什么时候用“主扩展模式”?

对象的个数不多;各个对象之间的属性有一定差别;各个对象的属性在数据库设计阶段能够完全确定;各个扩展对象有独立的、相对比较复杂的业务处理需求,此时用“主扩展模式”。将各个对象的共有属性抽取出来设计为“主表”,将各个对象的剩余属性分别设计为相应的“扩展表”,“主表”与各个“扩展表”分别建立一对一的关系。

2. 什么时候用“主从模式”?

对象的个数较多且不固定;各个对象之间的属性几乎没有差异;对象的属性在数据库设计阶段能够完全确定;各个对象没有独立的业务处理需求,此时用“主从模式”。将各个对象设计为“从表”的记录,与“主表”对象建立一对多的关系。

3. 什么时候用“名值模式”?

对象的个数极多;各个对象之间的属性有较大差异;对象属性在数据库设计阶段不能确定,或者在系统运行时有较大变更;各个对象没有相互独立的业务处理需求,此时用“名值模式”。

4. 什么时候用“多对多模式”?

两个对象之间互为一对多关系,则使用“多对多模式”。

数据库物理模型设计的其他模式

除了上面提到的四种主要设计模式,还有一些其他模式,在某些项目中可能会用到,在这里先简单做个说明,暂不做深入讨论,等到以后的项目用到这些模式的时候,再结合实际需求详细解说。

(一)继承模式

继承模式,可以看作是“主从模式”的一种特殊情况(或者说是“变形”),它所代表的两个对象也是“一对多”的关系。它与“主从模式”的区别是,“继承模式”中从表的主键是复合主键,并且复合主键中必定包含主表的主键列。

根据从表继承主表的列的数量,继承模式又分以下两种情况:

1. 从表继承主表的全部列

图7

在这种情况下,从表除了代表自身的专用字段以外,还冗余了主表的全部字段。这种设计方式的缺点显而易见:

  • 数据冗余度大

  • 一致性差

  • 磁盘存储量大

它的优点也显而易见:

  • 正因为它的冗余度大、所以它不易丢失数据。假设主表数据丢失、或者被误操作删改,也能依据从表数据重新生成主表数据;这种设计方式,可以在发生数据损坏的时候从应用的角度进行一定程度的数据恢复,等于是在SQL Server数据库级别的数据恢复功能之上又加了一道保险。

  • 正因为它一致性差、主表数据被重复存储,所以可依据外键关系进行数据验证。将主从表记录作关联比较,如果数据不一致,就可以得知数据要么被人为改动,或者要么程序代码中存在bug。

  • 尽管磁盘存储量大,但是数据在查询统计的时候,只需针对从表进行搜索即可,无需关联操作,可以加快检索的速度。这就是数据库模型设计中经常提到的“以空间换时间”。

2. 从表只继承主表的主键列

图8

这种设计方式,从表只继承了主表的主键列,这种方式的优缺点与前面刚好相反。

优点:

  • 数据冗余度小

  • 一致性高

  • 磁盘存储量小

缺点:

  • 正因为它的冗余度小、所以它易丢失数据。假设主表数据丢失、或者被误操作删改,就只能通过SQL Server数据库级别的数据恢复操作来找回丢失的数据了。

  • 正因为它一致性高,所以无法进行应用程序级的数据验证。

  • 由于采用了一致性设计,磁盘存储量较小,但是数据在查询统计的时候,必须要对两个表进行内连接(INNER JOIN)操作,才能搜索到相关数据。而内连接操作时需要耗费一定的时间的。这就是数据库模型设计中经常提到的“以时间换空间”。

当然,在实际的数据库模型设计过程中,还会有介于上述两者之间的第3种情况出现,那就是从表继承了主表的主键列以及部分其他列。这就要求我们设计人员要依据实际的业务需求进行综合分析、权衡、折中,给出最符合业务需求的设计结果。

(二)自联结模式

自联结模式,也可以看作是“主从模式”的一种特殊情况(或者说是“变形”),它在一张表内实现了“一对多关系”,并且可以根据业务需要实现“有限层”或者“无限层”的主从嵌套。

这种模式用得最多的情况就是实现“树形结构”数据的存储,比如各大网站上常见的细分类别、应用系统的组织结构、Web系统的菜单树等都能用到这种模式。

自联结模式有很多变体,且每种变体的优缺点同样鲜明。由于本连载的重点在于对跨行业通用数据库模型设计进行分析,所以对每种具体模式的细节方面的设计技巧不能作详细论述,请大家原谅。这里仅举两个例子说明:

1. 简单自联结

简单自联结,就是在一个表里设置当前类ID、父类ID,同时规定最顶层类的父类ID为一个固定值(比如0),在生成树的时候使用递归算法,记录的前后顺序通过“排序号”字段来确定。

图9

这个表用来存储菜单树很方便。首先会有一个主菜单,主菜单下有子菜单,子菜单下面又有孙菜单……菜单的数量不确定、层级不确定,用户可以在任意菜单下增加新的子菜单,或者删除某个子菜单及其下的所有孙菜单……这种设计方式很多人都会用到,短小精悍、维护方便、且完全满足用户需求,而且树的层次不限,扩展起来非常容易。这些都是它的优点。

它的缺点就是树结构的生成由于使用了递归算法,必然要对该表进行多次读取(读取的次数 = 表内的记录数 – 最深层级的记录数),多次读取就来了比较低的运行效率,当表里的记录很多的时候,这个缺点可以称得上是致命的。

于是就有了下面的这种设计模式。

2. 扩展自联结

扩展自联结,与简单自联结的最大区别就是通过附加冗余字段来避免递归运算,所要实现的主要目标就是一次读取就能生成整个树,一次提高树的生成效率。

但是,鱼与熊掌不可兼得,凡事都有两面性。

生成树的效率提高了,增删改表内记录的算法就会相应复杂,并且树的层数也变为有限的了。

所以在此类设计的时候,大家还是要认真分析业务需求,看看实际业务的重点在什么地方,然后再作具体设计。比如一些门户网站在首页显示产品类别是业务重点,那么我们在设计的时候就要尽可能的提高生成树的效率,采取扩展自联结模式;相反,一些基于Web的业务系统,要求对菜单树的增删改维护操作尽量简单,由于菜单的数目不多,所以菜单树的生成效率不是瓶颈,那么我们设计的时候就可以采取简单自联结模式。

关于附加冗余字段实现扩展自联结的方法很多,网上也有很多这方面的帖子,大家可以到Google上搜一下。

在这里仅举一个例子如下:

图10

这个设计与前面的设计最大的区别就是排序字段,前面的简单自联结用了一个整数型的字段来实现排序,这里用了一个Varchar20型的字段“层级代码”来实现大排序。这个字段的取值两位一组,代表一层,假定最深为5层,初始值为0000000000。

按照这样的设计,表内的数据记录可能就是这样的:

ID           TypeName           ParentID            TypeLevel

1 根类别               0                 000000

2 类别1                1                 010000

3 类别1.1              2                 010100

4 类别1.2              2                 010200

5 类别2                1                 020000

6 类别2.1              5                 020100

7 类别3                1                 030000

8 类别3.1              7                 030100

9 类别3.2              7                 030200

10 类别1.1.1            3                 010101

……

现在按TypeLevel字段进行排序,执行如下SQL语句:SELECT * FROM TMP_Type ORDER BY TypeLevel

列出记录集如下:

ID           TypeName           ParentID            TypeLevel

1 总类别               0                 000000

2 类别1                1                 010000

3 类别1.1              2                 010100

10 类别1.1.1            3                 010101

4 类别1.2              2                 010200

5 类别2                1                 020000

6 类别2.1              5                 020100

7 类别3                1                 030000

8 类别3.1              7                 030100

9 类别3.2              7                 030200

……

在控制显示类别的层次时,只要对“层级代码”字段中的数值进行判断,每2位一组,如大于0则向右移2个空格。

(三)单表模式

单表模式,就是把相关子类的属性统统集中在一个表里,通过“类别”字段来区分表内记录所属的子类以及该类的有效属性。在实际案例当中,单表模式的应用还是很广泛的。举个例子,有车的朋友现在拿出你们的《中华人民共和国机动车行驶证》,翻到“副页”,看看副页登记的档案指标。下图为推测设计图、不代表真实设计。

图11

我是2006年2月份买的车,我的机动车行驶证副页记载了如下档案指标:“号牌号码、车辆类型、总质量、整备质量、核定载质量、准牵引总质量、核定载客、驾驶室共乘、货箱内部尺寸、后轴钢板弹簧片数、外廓尺寸、检验记录。”浏览本文的朋友可以跟自己的行驶证对照一下,尤其是老司机,看看若干年前发的行驶证和现在的有没有区别。

在这里大家可以很清楚的看到,上述指标中“号牌号码、车辆类型、总质量、整备质量、外廓尺寸、检验记录”是各种类型的车辆的公共属性,“核定载质量、准牵引总质量、驾驶室共乘、货箱内部尺寸、后轴钢板弹簧片数”是货运车辆的专有属性,“核定载客”是客运车辆的专有属性。

根据经验我推测,就此种表现形式而言,公安机关交通管理部门的计算机系统应该就是采用“单表模式”进行的设计。通过这一个表就可以容纳包括货车、客车、轿车、摩托车、农用车等所有类型的车辆档案。这里面有一个“车辆类型”属性,这个属性就是用来区分当前记录所属类别,程序代码根据这个属性的值来确定当前档案记录的哪些属性是有效、且需要记录和打印的,哪些属性对当前记录来说是无效的。比如我行驶证上的车辆类型是“轿车”,除公共属性以外,只有“核定载客”指标打印了一个“5人”字样,其余指标打印的都是“–”字样,因为这些指标都是货车才有的,对轿车而言是无意义的。

这种设计有一个明显的好处——如果事先对车辆档案都有哪些指标调研的很充分,且后期基本上不需要扩展,那么系统运行时无论遇上什么类型的车辆档案都不需要变更程序,具有很强的通用性。很明显,行驶证是套打的,这种设计便于大批量的制作证件底单。因为不管什么类型的车辆档案都是这个格式,只要开动机器印刷便是。

凡事有利就有弊,这种设计的弊端也很明显。首先是给人的印象不是很人性化。我明明买的是轿车,你发给我的证件搞那么多货车的指标在上面干嘛?浪费版面!其次,如果一旦后期需要对某种类型的车辆档案扩充属性(无论你前期的需求调研多么充分,都不能保证以后不会变化),假设国家新颁布一部法规或者国标,要求必须在行驶证上记载客车的“发动机排量”、其他类型的车辆不作要求,那么按照这种设计,所有车辆(包括“无辜的”货车在内)都要换证(呵呵,不知道这种换证收不收工本费)!

声明:以上例子仅仅是我的推测,用以说明“单表模式”。我没有交通管理部门的工作经历,如果与实际情况不符,欢迎同行批评指正。

五、通用数据库模型分析

在我们数据库模型设计领域,有一个很有趣的比例——“5:3:2”,我称之为“五三二法则”。具体是什么含义呢?

这个“5”,表示大约50%的功能设计是跨行业通用的,不管你是做商业流通项目、还是电子政务项目、或者是网上拍卖系统,虽然各个项目所属行业不同,但是它们中的50%的设计思想都是相同的,这就是本章(第五章)所要讲述的“通用模型设计”。换句话说,本章所讲述的模型设计思想,你可以在任何项目中使用;“3”表示在某行业内部的各个不同的公司之间又有30%的设计思想是相同的,这就是下一章(第六章)所要说明的“行业模型设计”;“2”表示余下的20%才是某一特定公司所独有的业务需求,这部分内容我这个连载就不介绍了。

(一)人与组织

大部分的企业系统都会记录有关人与组织的信息,比如公司员工、供货商、销售客户、网上客户、会员卡客户、子公司、公司内部组织机构等信息。而大部分系统对这些信息进行数据库设计时,都是创建各自独立的表,没有抽取共有信息,因而造成了数据的冗余。

比如A公司为了开辟销售市场,通常会给与会员卡客户一定的优惠条件,那么显然不能排除持卡人为公司员工这种情形、也不能排除持卡人为公司的供货商这种情形。

假设B公司既是A公司的供货商、同时也是A公司的会员卡客户,如果按照各自独立的方式设计数据库表,则B公司的信息,如公司名称、联系人、联系电话、联系地址等信息必然要在A公司的“会员卡客户”表及“供货商”表作重复记录。如果B公司的某些信息,如联系电话和地址发生了变更,则A公司必须将会员卡客户表和供货商表中的记录同时修改,否则业务人员就会发现同一家公司(B公司)的信息,在系统内两个地方登记的不一样,那么,哪个是正确的?业务人员就会对软件数据的准确性产生怀疑,进而对整个系统持怀疑态度。

同样道理,在一些大型企业,内部各部门、各子公司之间有时也会按照销售的方式进行业务往来。“生产车间”是“产品营销部”的供货商,“生产车间”同时也是“原料采购部”的销售客户。有些大型企业集团还会建立集团内部银行,内部各部门、各子公司之间通过内部转帐支票的方式完成内部往来结算,各部门、各子公司之间采取相对独立的成本核算的经营方式。这样的话,如果每个部门、子公司都自己创建一套独立的“组织”表,那么同一个组织的信息就会存储在多个表里,这样就产生了数据重复。

如果同一数据重复存储在多个表里,一旦某个部门由于业务的发展而发生变更的时候,就会给系统使用者带来很大的麻烦——不但增加了数据修改的工作量,还必须要对所做的多处修改进行认真核对,以保证同一部门的信息在软件的各个地方都是一致的。

综上所述,为了解决数据在系统内部不必要的重复存储问题,关于这方面的数据库设计模式就用到了我们前面提到的“主扩展模式”

1. 人员

在需求调研和分析阶段、通常需要制作用例图,记录当前应用系统Actor及用例。假设系统Actor包括公司员工、签约人、供应商联络员、客户联络员4种类型,那么有些系统可能就会设计成4个表来分别存储。如果某个人所属的公司既是本公司的供应商又是客户,他则既是供应商联络员、又是客户连锁员,那么这个人的信息就要在“供应商联络员”、“客户联络员”表里各存一条记录,这就造成了数据冗余。

为了避免这种情况,我们采用“主扩展模式”进行设计,首先抽取4种Actor的共同属性创建“人员”表,然后创建其余4个表存储主表的扩展属性。

在这一节,我们重点讲述主表、即“人员”表的设计细节,其他表的设计放到后面再说。

图12

人员表的主键为什么是“当事人ID”,而不是“人员ID”呢?这个问题留到第3节讲述,此处只要把它看作是人员表的主键就可以了。

在设计人员表以及其他实体表的时候,需要注意实体属性的类型。一般可以分成三类:(1)自然属性,与该实体紧密相关,除非录入错误,否则不存在修改的情况;(2)社会属性,与实体松散相关,会随着实际情况的变化而变化;(3)系统属性,与实体基本无关,属于系统控制层次的属性。

对照上面的“人员表”我们可以看到,对于确定的一个人,排除录入错误的情况,他的“出生日期”、“身份证号”是不会发生变化的,这就是一个人的自然属性;除此以外的其他属性都是社会属性,都有可能会发生变化。大家都知道目前国内改名的现象比较多,尽管公安机关的限制很严,依然挡不住改名的热潮。至于身高、体重、职务、婚姻状况,就更不用说了,就像美女的脸、说变就变。护照一般都有有效期,过了有效期需要重新领新的护照,号码自然会发生变化。为什么“性别”也会变化呢?其实“性别”的取值范围是有国家标准的,取值分别是“男性”、“女性”、“未知的性别”、“未确定的性别”,感兴趣的朋友可以在网上搜搜。一般在录入人员信息的时候,如果不知道该人是男是女,默认应当选“未知的性别”,等以后知道了具体性别再作修正;此外男变女、女变男也不是什么新鲜事了,所以人的“性别”也是社会属性。

说了这么多,大家可以发现,一般的“社会属性”的名称前都有一个“现”字,用来提示大家这个属性是社会属性,是很可能会发生变化的,目前数据库里存储的只是最后一次修改的结果。有些系统(例如我举的这个例子)不关心这些属性的变化过程,只要有一个最新值就可以了。有些系统则必须跟踪属性变化的全过程,例如“干部考评系统”,可能就要记录某人“职务”变化的全部历史;“海关出入境登记系统”,就要记录一个人“护照”号码所有的变化;“康体中心”可能会记录其会员在一段时间内每天的“体重”变化值,等等。这样,就需要采用“主从模式”或者“名值模式”来进行相应设计,而不是简简单单的建一个表完事了。

但是无论是什么系统,在记载人员的“自然属性”的时候,由于其不会产生变化历史,所以只要作为“人员”表的字段就可以了。

“系统属性”,属于系统控制手段的范畴。例如有些系统对人员管理较严格、不允许随便增删人员记录,就在人员表中设置一个属性——“状态”,取值范围是“新增/有效/无效”。办事员录入一个人员信息后,“状态”属性被设置为“新增”,由管理人员审核通过后属性改为“有效”,只有状态为“有效”的人员才能进行相应的业务处理;当需要删除人员信息时,将“状态”属性改为“无效”。这就是实体的系统属性。

对于一个属性而言,它到底是“自然属性”、还是“社会属性”,需要我们在作需求分析阶段认真分析业务、与一线业务人员充分沟通之后予以确定。而“系统属性”一般是在系统设计阶段、结合业务逻辑确定。

2. 组织

事实上,“组织”的含义是很广的,分销渠道、供应商、公司内部的部门、政府机构、行业协会、行业主管部门、合作伙伴、竞争对手、家庭、团队……都是组织的一种;但同时,针对某一特定企业的业务需求而创建的应用系统,其需要打交道的“组织”的类型又是有限的。我们的设计人员所要做的就是在需求调研、分析阶段把所有需要与之打交道的组织类型确认清楚,这些工作绝不能放到后期程序运行时才去考虑。

个人感觉目前有一个倾向,有些人刚入行、设计经验不多,设计的系统很少考虑到扩展性,企业业务需求稍有变化、设计成果就要大幅度修改,甚至要推倒重来!由于饱受折磨,以致于当他做过一些系统、有了一定经验的时候,又特别倾向于设计一个“功能极其灵活的”系统、这个系统运行时可以自动适应分析阶段“考虑到的”和“没考虑到”的各种情况。这些人整天挂在嘴边的就是“即使将来有新的业务需求,我的设计也不用修改……”

个人观点:前一种情况情有可原,后一种思想不可取。如果系统设计的过于“灵活”,相应的该产品更像是一个“半成品”,而不是一个应用系统。因为这样的系统在使用中必然存在相当多的配置参数、有的甚至需要二次开发!这些都是用户不能忍受的!

若干年前我曾经遇到一个雄心勃勃的同行,他宣称要开发一个适应所有行业的MIS系统,只要这个系统开发出来、社会上所有行业都不再需要专门的管理软件,只要在他的软件里设置“一些”参数就可以正常使用了。他甚至还为此专门成立了一家公司。开始的时候他说他的软件针对每种行业只要事先设置四五十个参数就可以了,一段时间之后参数的个数涨到三百多,再过一段时间他的软件的功能是如此灵活、以致于针对某种行业的“实例化”版本需要专门的“二次开发”,又过了一段时间该公司倒闭、人去楼空。按照他的做法,再进一步的抽象下去,那么最终他的产品就不是MIS软件、而是PowerBuilder或者C#这类的开发工具了。

需求分析阶段的工作做得不细致,指望设计阶段设计出一个极其智能、其功能涵盖了所有已想到和没想到的需求的系统,本身就是不切实际的。

在外行人眼里看来,计算机很聪明、无所不能,而事实上无论多么聪明、多么智能的软件,它的每一步运算都是系统设计人员在事先就设定好的,如果系统设计人员事先没有设计和开发,计算机连“1+1”这么简单的事情也绝不会主动去做!不会出现没有设计而凭空跳出来的“功能”!如果有的话,我们称这种功能为“BUG”。

言归正传,在设计“组织”表的时候也不例外,在需求调研分析阶段就应当把与当前企业有关系的所有类型的组织及其各自的属性都调研清楚,然后才进行下一步设计。

“组织”表的设计与“人员”表类似,也要考虑两大问题——

  • 避免数据冗余存储

  • 区分属性类别

图13

大家看到上面这个设计的第一感觉是什么?

“胖子刘有没有搞错?组织表就这么几个属性?”我猜大部分都是这个想法吧?没错,在我的设计中组织表就这么简单。至于大家认为通常应当具有的地址、联系人、联系方式、各种许可证件信息等等,我都设计到其他表里了。原因在后面会详细叙述,敬请关注。

3. 当事人

在前两节,组织表和人员表的主键都是“当事人ID”,这是什么意思呢?

答案:“当事人ID”是“当事人”表的主键,“当事人”是“组织”和“人员”的超类,是二者的公共属性。

因为,组织和人有许多属性都是相同的,如信用等级、地址、邮编、联系电话等;组织和人在某些场合都扮演了相同的角色,如公司产品既可以销售给个人、也可以销售给组织,两者都是“客户”。所以,利用前面提到的“主扩展模式”,模型设计如下:

图14

在这个模型中,组织和人员采取了相同的主键生成规则、统一编号,存放在“当事人”表中,在与其他模块联系时,简单的使用“当事人”与之建立关系,可以减少很多设计和开发工作上的复杂性。下文凡是提到“当事人”的时候,表示当时的上下文情况对“组织”和“人员”均适用。

4. 当事人类别

企业系统在记录当事人信息的时候,总会记录其所属类别。这样可以分门别类的统计各类当事人的业务信息、便于经营数据的分析和对比,为公司经营战略提供决策依据。

关于当事人的分类方式很多且不固定。比如按照人员年收入水平可以作如下分类——1万元以下、1万-5万元、5万-10万元、10万-20万元、20万元以上;按照组织的规模可以分为——特大型企业、大型企业、中型企业、小型企业和个体工商户等。

面对以上情况,有些系统在设计的时候,喜欢在当事人表中增加“年收入水平”和“组织规模”两个字段,分别用于存储当事人所属类别信息。这种设计很死板且不方便扩展。比如企业根据业务的需要新增了一种分类方式:按照行业类别分类如下——通信、IT、能源、电力、制造业、物流运输、政府机关、零售业等。采取这种设计必要修改表结构,增加一个“行业类别”字段,相应的程序代码也要随之修改。

在我们这里,“当事人”和“当事人类型”是典型的多对多关系,由于关联表有独立的业务需求(企业希望保持当事人类别变动历史,以便进行更详细的分析),所以我们采用“多对多模式”的第1种情况设计如下:图15

为了满足企业的这种需求,我们在关联表(当事人类别表)中增加了起始和截止时间两个属性,记录当事人类别的变化历史。例如,假设某当事人在2004年1月1日至2004年12月31日这段时间的收入水平为“1万元以下”,在2005年1月1日至2005年12月31日这段时间收入水平增加到“1万-5万元”,在2006年1月1日至2006年5月22日这段时间的收入水平又下降到了“1万元以下”,这样当事人类别表中可添加3条记录。其中第1条和第3条记录针对外键字段(当事人类型ID、当事人ID)来说是重复的,但是外键字段加上起始日期后,就不再重复、可以作为整条记录的唯一标识,所以我们在这3个字段上创建了一个唯一索引。

如果企业不需要变动历史,只关心当前最新状态,那么“当事人类别”表中的起始和截止日期删掉即可。为了简化开发,还可以删掉“当事人类别ID”字段,只保留“当事人ID”和“当事人类型ID”、并在这两个字段上创建主键即可。

“当事人类型”表是一个“简单自联结模式”的表,通过这种模式来记录企业所有的当事人分类名称和分类项目,同时标记分类名称为非叶子(是否为叶子属性值=false)、分类项目标记为叶子(是否为叶子属性值=true)。

按照当前所举的例子,“当事人类型”表存储的数据记录为如下形式:

当事人类型ID
描述
排序号
父ID
是否为叶子
1
年收入水平
2
NULL
0
2
组织规模
3
NULL
0
3
行业类别
4
NULL
0
4
1万元以下
1
1
1
5
1万-5万元
2
1
1
6
5万-10万元
3
1
1
7
10万-20万元
4
1
1
8
20万元以上
5
1
1
9
特大型企业
1
2
1
10
大型企业
2
2
1
11
中型企业
3
2
1
12
小型企业
4
2
1
13
个体工商户
5
2
1
14
通信
1
3
1
15
IT
2
3
1
16
能源
3
3
1
17
电力
4
3
1
18
制造业
5
3
1
19
物流运输
6
3
1
20
政府机关
7
3
1
21
零售业
8
3
1

5. 当事人角色

在这一节里,我们重点说一下,“角色类型”、“当事人角色类型”、“当事人”以及“当事人角色”这几个概念之间的关系。

其实这部分内容在国内的企业管理系统设计中多数属于权限控制部分。通常的权限控制的设计思路都是这样的:首先在系统开发时登记注册所有功能模块及代码,系统运行时由系统管理员来维护角色列表、并为每一种角色设定可访问的模块(角色和模块相关联)。业务人员登录系统后,系统读取当前用户所属的角色,并依据前述设定读取这些角色能够访问的所有模块、形成一个模块代码列表,存储为CS系统的全局变量或者BS系统的Session。每当用户试图进入一个功能模块前,系统都判断一下当前模块代码是否包含在“模块代码列表”中。如果包含,说明该用户有权限,允许进入;不包含,则说明该用户无权操作此功能模块。以此实现权限控制。

关于权限控制,这里先简单提一下,在这一章的最后一节我们来详细描述应用系统的权限控制。

回过头来说当事人角色。在这一节里我们所提到的当事人角色,主要的设计目的是为了满足特定的业务功能需求、描述当事人出于特定身份的情况下能够进行哪种业务。

举例说明:众所周知,“客户”代表一种身份,具备这种身份的人或组织(我们统一称之为“当事人”,这也从另一个方面体现了将“人员”和“组织”概括为“当事人”的好处)能够付钱给企业购买产品或者服务;“VIP客户”则是在“客户”的基础上更一步,能够享受更优惠的折扣或者更特别的服务,同时也意味着它能够给企业带来更大的效益或者更特别的社会关系。“供应商”是另外一种身份,具备这种身份的当事人给企业提供原材料、产品或服务以换取货币。

“客户”和“供应商”代表了两种不同类型的业务工作,需要不同的工作流程,当企业做到一定规模,客户和供应商的数量达到一定的数量级时,企业实际上是针对各种不同的角色来开展日常业务、而不是针对具体的某个人。例如肯德基、麦当劳每天食客上千,它实际上就是“堂食”和“外卖”两种角色的客户提供服务。不可能说张三来了提供一种服务,李四来了就换一种方式服务,过一会儿王五来了再为他奉上一种全新的业务!那样的话啥也别干了,累也累死了。

说到这里,再多说一句题外话。一段时间以来我总在想,我们中国的饮食文化源远流长,中餐馆遍布全球,为什么就没有一家类似肯德基、麦当劳一样驰名世界的饮食品牌呢?为什???五千年的时间除了打仗就是吃饭,到了今天更是发展到极致。什么花鸟虫鱼、珍禽异兽,统统都能端到饭桌上;电视里的明星访谈节目,无论嘉宾是歌星、影星、教授、体育……提到“吃”,首先都是会心的一笑,然后就滔滔不绝的给你讲上小半天。看到这帮人提到“吃”时的那副故作聪明的样子,我就想,如果你们能把数理化、天文、地理、生物、IT、经济、法律、制度等行业知识随便挑一样熟悉到这种程度,那我们的综合国力早就超过老美了。老美的专家在研究怎么样去撞击彗星,我们的“专家”在研究怎么吃果子狸,嘿嘿,这不是差距吗?某主持人连普通话都说不好,却愣是靠着主持一档饮食节目,在CCTV混了个风生水起。

天天谈吃、人人谈吃,为什么就没有一个享誉全球的饮食品牌?想了好久,终于让我想到一点端倪。我这个答案一公布出来,估计很有可能会在国内的饮食行业造成一场地震。如果哪位业内人士从我的答案受到启发,进而引发一场饮食行业的大洗牌、并创造出一个崭新的世界级餐饮品牌,到时候别忘了在记者招待会上注明出处——《胖子刘的数据库模型设计专栏》,谢谢!^_^

答案在这里暂时不公布,留待下节再说。嘿嘿,各位看官别忘了要经常来这里呀。

话题重新回到刚才说的,企业上规模之后的业务工作就是针对角色开展,而不再针对具体的客户,我们把这种情况叫做“大企业”;没上规模的企业,业务重点还是放在具体的客户身上,并为单个客户提供差异化服务或产品,这种情况我们称之为“小企业”。

小企业在设计当事人角色时,采用如下数据库模型设计:

图16_small

大家可以看到,左上角是“角色类型”表,这个表很简单,里面存储若干记录,每条记录描述一个角色类型。角色其实也有很多种类型,包括订单角色类型、协议角色类型、需求角色类型和当事人角色类型等。上图中的“当事人角色类型”表,就是对“角色类型”表的扩展,设计模式就采用了“主扩展模式”。按照这种模式,还应该有3个扩展表,只是这3个扩展表不是本章讲述的内容,留待后文书进行扩充。

“当事人角色类型”和“当事人”之间就是一个简单的“多对多关系”,道理很明显,一个当事人角色类型下面可以有多个当事人,一个当事人也可以从属于多个当事人角色类型。按照4大设计模式中的第4种“多对多模式”进行设计,就得到了“当事人角色”表。

关键就在“当事人角色”表。区分一个数据库模型是为大企业而设计、还是为小企业而设计,最主要的就是这个表。

如果一个企业是大企业,那么企业的日常业务是为角色服务的,换句话说,企业不为单个客户提供差异化服务,那么只需要“当事人角色”表存储某个当事人所属的角色就可以了,不必对该表再继续细化。

如果一个企业是小企业,那么企业必然要对某个当事人提供差异化服务,就需要对“当事人角色”表进行细化。就如上图所示那样。通过采用“主扩展模式”,以“当事人角色”表为主表,设计出5个一级扩展表,分别是“股东”、“潜在客户”、“人员角色”、“组织角色”、“客户”,其中“人员角色”、“组织角色”、“客户”3个角色又可以继续扩展出若干个二级扩展表加以详细描述。“人员角色”的扩展表包括“家庭成员”、“雇员”、“签约人”、“联络员”,“组织角色”的扩展表包括“供应商”、“政府有关部门”、“合作伙伴”、“竞争对手”、“组织单位”、“行业协会”、“内部组织”、“住户”、“分销渠道”,“客户”的扩展表包括“收货人”、“最终使用人”、“付款人”。而某些二级扩展表还可以继续扩展出三级扩展表,详见设计图,文字部分就不再描述了。

这么多的扩展表,关键之处在于这些扩展表都是基于“当事人角色”扩展出来的。在系统运行阶段,需要为每个当事人的每一种角色设置进行详细记录。例如,某企业有1000个当事人,平均每个当事人具有2个角色,那么总的当事人角色就有1000*2=2000个,这2000个当事人角色都需要详细维护。以此提供针对当事人的差异化服务或产品。

大企业由于只是针对角色提供服务,所以在设计当事人角色时,将上图的“股东”、“潜在客户”、“人员角色”、“组织角色”、“客户”五个表的外键关系改到“当事人角色类型”表上、“组织角色”表删除其它所有字段仅保留“PARTY_ROLE_ID”即可,其它不变。同样的例子,如果该企业的当事人角色类型总计有20种的话,企业只需要对这20种角色类型进行维护即可,企业针对同一角色的所有当事人只需实现同质服务即可。此为针对角色的服务。

后期进入到业务功能设计阶段后,针对哪个角色类型开展业务,就只需针对该表设计扩展表或者子表,记录相应的业务信息即可,对于业务的扩展是很容易实现的。

图16

6. 当事人权限

书接上文,在这里占用一点篇幅,描述一下目前比较常用的“基于角色的权限控制”设计方法。如果有人有更好的设计,欢迎提供。

“基于角色的权限控制”,核心思想是将权限控制通过当事人所属角色来实现,当事人本身并不直接绑定权限。有关当事人直接绑定权限的设计,不在本文的描述范围之内。

设计思路都是这样的:首先在系统开发时登记注册所有功能模块及代码,系统运行时由系统管理员来维护角色列表、并为每一种角色设定可访问的模块(角色和模块相关联)。业务人员登录系统后,系统递归读取当前用户所属的全部角色,并再次递归读取这些角色能够访问的所有模块资源、形成一个模块代码列表,存储为CS系统的全局变量或者BS系统的Session。每当用户试图进入一个功能模块前,系统都判断一下当前模块代码是否包含在“模块代码列表”中。如果包含,判断其是否存在“拒绝”属性,“拒绝”优先于“授予”。如果没有“拒绝”属性,只有“授予”属性,说明该用户有权限,允许进入;否则,说明该用户无权操作此功能模块,程序给出权限不足的提示,并退出。以此实现权限控制。

设计图如下:

图17

上文还留了个关于“饮食”的小尾巴,现在补全^_^

一段时间以来我总在想,我们中国的饮食文化源远流长,中餐馆遍布全球,为什么就没有一家类似肯德基、麦当劳一样驰名世界的饮食品牌呢?

答案是:我们中国的菜肴不能量产、饮食文化过于强调品种和花样,而且,越是高档的珍馐美食,其烹饪技术越是复杂,越是依赖于某个特定厨师个人的烹饪能力!

这种现状非常不利于美食的广泛传播。有许多美食都是独此一家、别无分店。你对某道菜有好感,只能到他店里去吃;别的饭店即使有同名的菜,味道也肯定不一样。对于食客来说,即使是王母娘娘的蟠桃盛宴,如果很难吃到,时间长了也就失去了兴趣;对于饭店来说,品种越多、烹饪技术越复杂,就越不容易量产。厨师个人的能力终归是有限的,即使是“食神”,他一天又能做几道菜?又有几个人能品尝到?又会有多少口碑?又如何流传世界?

反观肯德基、麦当劳,一共就那么几样食品,但全都是机械化生产,有严格的生成工艺和标准,经过培训后的“厨师”(我觉得还是叫机器操作员更形象一些)做出来的食品,其样式、外观、口味全都一样,随便到哪个城市的哪家分店都能享受到相同的体验。

纵观流传世界的品牌,如可口可乐、百事可乐、肯德基、麦当劳、Windows操作系统……共有的特点都是品种少、可量产。

国内餐饮界符合“品种少、可量产”标准的,个人感觉当属沈阳的“四季面条”。我原来住在沈阳市东陵区的泉园小区(我3年前搬来北京),小区附近就有一家“四季面条”,很符合我上面所说的标准。不但是饭店经营的品种,就连饭店本身都可以量产!一段时间以来我一直有一个想法,就是把沈阳的四季面条开到北京来,按照我的思路有朝一日当可开到全中国、乃至全世界。可惜,个人有限的资金都给房贷做贡献了,不知何时才能得偿所愿!

闲言少叙,本章《通用数据库模型分析》的第一部分——“人与组织”到此结束。

做个预告:本章第二部分将重点讲述“产品”模型,第三部分是“订单”、第四部分是“销售发货”、第五部分是“采购”,第六部分是“财务”,敬请关注。

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s