从AdventureWorks学习数据库建模——实体分析

原文:从AdventureWorks学习数据库建模——实体分析

最近打算写写数据库建模的文章,所以打算分析微软官方提供的SQL Server示例数据库AdventureWorks,看看这个数据库中有哪些值得学习的地方。

首先我们需要下载安装一个SQL Server数据库引擎,然后下载示例数据库,这里笔者用的是SQL2008R2,所以下载的是AdventureWorks2008R2,下载地址:

http://msftdbprodsamples.codeplex.com/

下载数据库后附加到SQL Server中即可看到这个数据库。

这是一个自行车制造和销售公司的数据库,该公司建立自己的销售网站,提供在线销售。首先看看这个数据库的结构,其建立了多个Schema,通过 Schema来划分表所在的模块,比如HumanResources,Person,Production,Purchasing和Sales。如果是非 常通用的表,比如日志表,那么就不属于任何模块,使用系统默认的Schema:dbo。

对于这么一个复杂的模型,我们可以按照:主要实体、附属实体、事务实体关联关系的顺序进行分析。

主要实体

对于整个系统来说,BusinessEntity是最核心的实体,用于表示一个“人”,这里的人是打引号的,因为它既可以表示真实的自然人,也可以 表示:公司、组织甚至一个商店,可以认为是一个法人。对于这个数据库模型来说,有3个实体继承自BusinessEntity,那就是 Person,Store,Vendor。

 

对于Person自然人来说,他可能是公司的员工,也可能是客户,所以我们又关联出了两个实体Employee和Customer。这里需要注意的 是,在这个模型中,他并不把一个自然人标识为一个客户,而是对不同的Store,会形成不同的客户。也就是说对于公司来说,他并没有客户主数据,同一个人 在不同的店消费,那么就会在不同的店中记为一个客户。为什么要这么设计,确实很奇怪,可能是业务上的需求吧。

 

这里延伸到Employee,就可以把HumanResources下面的实体分析一下,很显然Department是主要实体,至于 Employee和Department之间的关系,我们接下来再分析,这里我们只找主要实体。Employee如果在销售部门,那么就是一个 SalesPerson,所以这个实体是继承自Employee。另外在Production中还有一个很重要的实体Product,用于表示生产和销售 的产品。

 

附属实体

所谓附属实体,就是依附于主要实体而存在,对主实体的属性进行补充的实体,如果主要实体不存在,那么附属实体里面的数据就没有意义。对于前面找的主要实体,我们一个一个的分析:

BusinessEntity

BusinessEntity有两个附属实体:BusinessEntityContact和BusinessEntityAddress,对于联 系人实体,是和Person形成多对多关系,所以BusinessEntityContact是多对多产生的中间表,另外再加上ContactType说 明联系人的类型。而对于业务实体的地址,系统也抽象出了一个Address表,使得BusinessEntity和Address之间形成多对多关系。

 

Person

对于Person表,关联的表分为两类,一类是一对多或多对多的普通关联表,比如一个人有多个PersonPhone,一个人有多个 EmailAddress,或者一个人持有多张信用卡PersonCreditCard。这里把CreditCard和Person设置成多对多的关系, 我想应该这里的CreditCard包含公司商务卡的情况,这种卡的真正持有人是公司,但是公司会派发给Sales用,如果Sales离职了,那么这张卡 会收回,派发给其他的员工用,所以这就形成了多对多关系。另外一类是一对一的拆分或继承关系,比如Password表。如果是简单的设计,我们完全可以把 Password相关字段放在Person表中,而这里却独立出来形成一对一关系,主要可能是以下几方面的原因:

安全考虑:Password的内容很机密,独立成表后可以单独对这个表进行加密,权限分配等。

性能考虑:Password的内容只用于登录系统时验证,以后接下来的所有查询都用不到这些字段,所以不放在Person表中,系统在查询Person表时就不需要连带着把不需要的字段查出来。

 

Employee

这里主要涉及到的是HumanResources下的表,除了员工的基本信息外还记录了员工的履历,工资变动,部门变动情况。一个Employee 对应多个JobCandidate,为什么是一对多关系呢?因为应聘者可以制作个人简历的多个版本,然后投公司的不同部门,最后如果应聘者被录取了,那么 就可以把JobCandidate中的BusinessEntityID设置为Employee的ID,如果应聘失败,那么 BusinessEntityID就是NULL。EmployeePayHistory是员工的工资表,但是不是发工资的记录表,只是记录员工的工资基本 信息,如果工资变动就创建一条新的记录。Employee和Department是多对多的关系,并不是因为一个员工身兼数职,在多个部门同时干活,而是 因为要记录员工的部门调动情况,所以保留了所有历史记录,形成了多对多关系。另外比普通公司的部门员工表不同,这个系统还有一个轮班表Shift,那是因 为这是个制造业公司也有门店进行销售,所以会分为早班,中班和晚班,一个员工的轮班是固定的,如果发生变化,比如以前是上夜班,现在改为上早班,那么 EmployeeDepartmentHistory中也会对应生成一条新的记录。

 

Sales

销售继承至Employee,主要有销售区域,销售配额等附加的属性。本身销售区域和销售配额可以看做是Sales表的属性,但是为了记录历史,所以独立出来了一对多的表:SalesTerritoryHistory和SalesPersonQuotaHistory。

 

这里需要说明一下SalesTerritory表并不是Sales的附属表,他本身是一个独立的实体。

Product

这个实体应该是各个主实体中属性最复杂的实体了。主要分为ProductModel和Product两块。

先说ProductModel,可以理解为样品,样机或者是模型,在进行量产前需要先生产ProductModel。对于 ProductModel,主要有产品的部件关系图Illustration和描述ProductDescription。ProductModel和 Illustration是普通的多对多关系,一个模型有多个部件关系图,一个部件关系图也可以用于多个样机中。而对于描述,除了普通的多对多关系外,还 增加了一个多语言的关系。于是增加了Culture表,形成了三个表的多对多关系。实际上这种多语言模型并不好,很容易产生错误,对于多语言的处理,可以 建立更好的模型。

 

接下来就是Product实体,可以将相关的表分为三类:

多对一:产品的分类Category和前面提到的ProductModel。

一对多:产品成本历史ProductCostHistory,产品的组成BillOfMaterials,产品的库存ProductInventory,产品价格历史ProductListPriceHistory,产品的复查ProductReview。

多对多:产品文档ProductDocument和产品照片ProductPhoto。

 

产品分类没啥好说的,就是普通的二级分类法,一级大分类在ProductCategory,二级小分类在ProductSubcategory,然 后所有产品都必须归属到二级小分类上。ProductCostHistory和ProductListPriceHistory都是因为要记录基于时间段 的历史而形成的一对多关系,其中必有StartDate和EndDate来划分时间区间。【历史数据记录】

关于产品文档和产品照片,由于存在复用的情况(比如产品的外观是一模一样的,只是某些内部参数不一样,那么产品照片就可以复用。)所以就形成了多对 多关系,有多对多关系就会有中间表。产品图片由于会有细节照片,各个角度的照片,所以在多对多关系表中另外定义了一个Primary字段用于说明当前选用 的照片是不是主体照片。

事务实体

前面分析的实体都是在主谓宾语句中当主语的对象,接下来我们要分析这些主语之间发生关联,进行事务操作后产生的宾语对象。

对于SalesPerson、Product、Customer在一起时,联想到的就是销售订单:

SalesOrder

只要是涉及表单的东西(销售订单、报销单、采购订单、发货单等)大部分情况都会分为Header和ItemDetail两个表,在销售订单中Header用于记录单据的销售的人员,客户,总金额等信息,而ItemDetail中记录了具体销售的产品,数量等信息。

下面先分析Header:

 

Header建立了SalesPerson与Customer的联系,另外还有范式化的一些字段,比如ShipToAddress,BillToAddress,ShipMethod等。除了这几个实体外,我们需要单独分析一下以下几个实体:

Territory,这是在前面介绍Sales的时候说到,这个销售区域和SalesPerson是有关联的,按理来说,Header表已经关联了 SalesPerson表,我们就可以通过SalesPerson获得其下单时对应的Territory,为什么还需要额外添加这个Header到 Territory的直接关系呢?这是出于性能的考虑而增加的冗余,对于有时效性的对象,最好是直接关联,而不是通过中间对象jion多个表去关联。让我 们看看如果没有直接关联Territory,那么我们的查询到底有多复杂:

select h.*,st.*

from Sales.SalesOrderHeader h

left join Sales.SalesPerson sp

on h.SalesPersonID=sp.BusinessEntityID

left join Sales.SalesTerritoryHistory sth

on sp.BusinessEntityID=sth.BusinessEntityID and h.OrderDate between sth.StartDate and sth.EndDate

left join Sales.SalesTerritory st

on sth.TerritoryID=st.TerritoryID

下面再来看看币种和汇率的相关表Currency。在这个系统中,Header并没有直接说明用什么币种付款,什么币种结算,汇率是多少,而是把这 几个字段放在CurrencyRate表中,通过引用CurrencyRate来表示。虽然说独立出来后没有直接放在Header表中直观,不过减少了冗 余,只需要做一次Join就能拿到结果,所以性能上还是能接受的。【虽然从关系上需要Join了CurrencyRate后再JoinCurrency表 才能完整,但是一般来说Currency表只用于CurrencyRate的限定,而不需要在查询时使用Currency表,因为 CurrencyCode是国际标准编码,只需要显示Code就够了。】

Header和SalesReason是多对多关系,在客户下单的时候让用户复选购买原因,是因为促销,还是看了杂志广告之类的,简单多对多关系,这个没啥好说的。

SalesOrderDetail

Header和OrderDetail是一对多关系,Detail记录了具体购买了啥产品,购买单价,数量等,所以关联的是Product,但是在 这个系统中,他并不是直接关联Product对象,而是在之间建立了SpecialOfferProduct,该表是Product和 SpecialOffer的多对多中间表。

 

为什么会这么做呢?这主要是跟具体的业务相关。产品在生产出来以后有一个标价Product.ListPrice,但是在实际销售中,商家会有各种 促销活动(比如买10个以上9.8折,25个以上9折等),所以会形成Product和SpecialOffer的多对多关系,维护了哪些产品能够有哪些 折扣。为了统一模型,如果产品不做任何打折促销,也会在SpecialOffer中维护一条记录“No Discount”。

这里有一个特别的技巧,SpecialOfferProduct是没有自己独立的主键的,而是使用ProductId和 SpecialOfferId作为联合主键,然后在OrderDetail引用具体的SpecialOfferProduct时,就会将 ProductId和SpecialOfferId引用到其列中。所以在模型上来说,是OrderDetail关联 SpecialOfferProduct,然后再关联Product,但是我们在实际查询中,完全可以忽略SpecialOfferProduct表,直 接用OrderDetail去Join Product即可,所以性能上没有任何影响,这是一个漂亮的设计。

而当Employee、Product和Vendor在一起时,联想到的就是采购订单:

PurchaseOrder

和销售订单类似,采购订单也 分为PurchaseOrderHeader和PurchaseOrderDetail。

 

系统中先使用ProductVendor定义了哪些Vendor能供应哪些产品,在生成采购订单时会基于这里面的内容来生成,但是在模型上并不直接 反应,因为Product属于Detail表,而Vendor是属于Header表,不能像前面说到的SpecialOfferProduct一样通过引 用来传递这种限制。

Header记录的是采购人员Employee与供应商Vendor的关系。一个采购订单Header中会包含多个明细Detail,里面记录了采 购哪些Product。采购订单比销售订单简单很多,最为买方,不会去记录促销,购买原因之类的信息。另外采购中没有涉及到币种汇率问题,我估计这是因为 产品都在国内采购和结算,所以只有一种币种,而销售是面向世界各地,所以涉及到币种汇率。

WorkOrder

除了前面说到的销售订单和采购订单外,在生产过程中还有生产订单,用于表示产品的生产情况。主要有WorkOrder和WorkOrderRouting两个实体。

 

WorkOrder记录了生产某个产品的数量、报废和时间情况,而WorkOrderRouting记录的是在某个产品的具体生产过程中有哪些工序,每个工序的时间、成本等情况。总的来说,这是一个非常非常简化的生产工作订单模型。

其他实体

除了前面说到的实体外,还有其他几个独立出来的实体需要说明一下:

TransactionHistory

另外还有一个其归档表TransactionHistoryArchive,其结构和TransactionHistory一模一样,这里面记录的 是生产工作订单或者采购订单或者销售订单这3个事务的产品、日期,数量等公共信息。这个表可以认为是一个事务的日志表,平时并不参与各个实体的查询,只有 在审计或者跟踪数据变化时才用到。

TransactionHistory表中的数据是在各个Order表上建立Trigger自动插入进去的,而不是由外部程序代码去控制。由于本身 事务表的数据量就比较大,而这个表却存了三个事务表中的数据,所以增长特别快,必须进行归档操作,把老数据搬移到另一个归档表中,这样才能保证查询新 TransactionHistory表的速度。

AWBuildVersion

这是一个记录当前数据库定义创建时数据库的版本也可以定义当前数据库定义脚本的版本。对于通用的产品来说,这个表比较重要,因为产品可能需要升级, 升级程序在升级前读取这个表,知道了当前数据库定义是什么个版本,然后就可以查询到将当前版本的数据库升级到新版的数据库所需要修改的SQL,然后执行这 些SQL。

而应用程序在运行时第一件事就是检查这个表中的版本信息,保证数据库定义的版本与程序要求的版本匹配,这样程序才能正常运行。

对于企业内部系统,一般只有一个实例,而且由企业内部的IT人员开发维护,所以这个表没有也没什么问题。

DatabaseLog

这是记录数据库DDL(数据定义语言,比如CREATE, ALTER, DROP等)操作的日志表。这个表是由Database Trigger自动维护,当在这个数据库中执行了DDL的时候,系统会触发Trigger,往这个表中记录一条数据。这是一个好东西!

 另外还有一些因为范式化抽象出来的码表,我在前面的模型中没有提到,比如CountryRegion,StateProvince等这些都比较简单,就不一一累述了。

这篇文章我只是简单分析了下实体和实体关系,下面一篇文章会进一步分析其中的细节,有哪些优缺点。 

 

时间: 2024-10-27 23:44:38

从AdventureWorks学习数据库建模——实体分析的相关文章

从AdventureWorks学习数据库建模——国际化

前一篇博客我已经把各个实体分析了一遍,从分析中可以看到,这个公司是做本地采购,生产,然后通过网站和门店进行国际销售的.所以这里会涉及到一些国际化的问题.接下来就来分析一下有哪些国际化需要注意的问题和数据库模型中的解决方案. 语言 AdventureWorks数据模型中,只有对ProductDescription进行了多语言设置.关于多语言的建模,我曾经写了一篇文章,详细介绍了多语言建模的几种方法,可以参考:http://www.cnblogs.com/studyzy/archive/2013/0

从AdventureWorks学习数据库建模——保留历史数据

在业务需求中,经常需要我们在系统中能够记录历史信息,能够查看到历史变动情况,这时我们可以通过增加开始结束时间字段来记录数据的历史版本.对数据的历史记录主要分为:关系.属性历史,实体历史和变更历史. 关系.属性历史记录 所谓关系历史记录就是指两个实体之间的关系存在历史版本.比如部门表和员工表,对于某一个时刻来说,一个部门有多个员工,一个员工只属于一个部门,所以是个一对多的关系.而我们希望把这个关系记录下历史变动,那么就会形成多对多关系.多对多关系就形成中间表,然后我们在中间表上加入"开始时间&qu

用实体关系图进行数据库建模 (无事可作,转贴)

用实体关系图进行数据库建模 (阿良@仙人掌工作室 2001年08月14日 15:37) 一.概述 很可能你现在正在规划一个数据库驱动的网站:而且几乎可以肯定的是,你一定已经浏览过数据库驱动的网站.过去,一些网站依赖CGI脚本和文本文件存储实现数据持久化,但现在我们能够访问大量不同的关系型.对象-关系型.面向对象型数据库.对于Web应用来说,关系数据库是一种强大的支持工具,这得感谢它们的高可用性.性能,而且相对来说,关系数据库比较容易使用.要找出一个功能完善.源代码开放.能够在多种平台上运行的数据

《威胁建模:设计和交付更安全的软件》——第1章 潜心开始威胁建模1.1 学习威胁建模

第1章 潜心开始威胁建模 谁都可以学习威胁建模,更进一步说,每个人都应该学习威胁建模.威胁建模是利用模型来发现安全问题,这意味着通过提取大量细节对安全问题进行全面检查,而不是代码本身.之所以要构建模型,是因为模型能让你在没构建系统之前即可发现问题,以及在问题出现前提早发现问题.最后,威胁模型可以预见可能侵袭你的威胁. 首先,威胁建模是一门实用科学,本章系统地描述其实用性.尽管本书为你提供了很多有价值的定义.理论.观点.有效的方法和技术,但你还是希望能将这些用于实践.因此,本章内容主要从实践经验展

Visual Studio .NET Enterprise Architect 中基于 Visio 的数据库建模:第一部分/1

enterprise|visual|数据|数据库 Terry HalpinMicrosoft Corporation 2001年11月 摘要:本文是介绍 Microsoft Visual Studio .NET Enterprise Architect 中基于 Visio 的数据库建模组件系列文章中的第一篇,重点介绍该工具提供的对象角色建模 (ORM) 支持. 目录 简介 创建新的 ORM 模型 使用 Fact Editor(事实编辑器) 添加句子类型 使用 Fact Editor(事实编辑器)

Visual Studio .NET Enterprise Architect 中基于 Visio 的数据库建模:第一部分/2

enterprise|visual|数据|数据库 图 5 为一个示例.图 5:使用 Freeform 输出样式的 Fact Editor(事实编辑器)(单击图像以查看大图片)为实体类型提供引用方案后,就不需要在以后指定事实类型时重复引用方案了.与实体类型不同,值类型(例如,EmployeeName [雇员姓名].RoomNr [房间号])没有引用方案,由于其实例仅为文字常数(例如,用于命名或引用实体的字符串或数字),因此它们可以标识其自身.在 Freeform 模式中,值类型通过附加空括号 [(

Spring Data 数据库建模最佳实践

本文节选自电子书<Netkiller Architect 手札> 出处:http://www.netkiller.cn 作者:netkiller , QQ:13721218, 订阅号:netkiller-ebook 第 12 章 Spring Data 数据库建模最佳实践 目录 12.1. 分类表 12.2. 为字段增加索引 12.3. 复合索引 12.4. 一对多实例 12.5. ManyToMany 多对多 12.6. 外键级联删除 ORM的出现解决了程序猿学习数据库学历成本,也加快了开发

教你使用数据库建模工具进行数据库建模

很多人在进行数据库设计的时候,还是喜欢使用word文档的格式设计好数据库结构以后,再进行物理数据库的创建:而真正使用数据库建模工具进行数据库设计的就很少了:如果你讯问那些不愿意使用数据库建模工具的人为什么的话,我想他们一般会给你下面几个答案: 1.数据库结构不复杂,没必要使用建模工具. 2.建模工具使用起来比较麻烦,不现实. 3.我们公司有专门的数据库文档格式,恐怕建模工具没法生成合适的文档. 对于以上答案,我认为都不成立,是因为对建模工具的误解造成的. 以powerdesign为例,对于上述答

数据库建模-数据库管理和使用需要掌握哪些要点?

问题描述 数据库管理和使用需要掌握哪些要点? 我接到一个关于数据库管理的项目,项目经理要求我掌握以下内容: 一.数据库设计管理人员 1. 根据要求设计关系数据库或对已有关系数据库改动. 2. 设计数据库运行规则: 区分不同用户,注册,根据实际需要,赋予不同权限,组织依权上传.下传及处理数据. 保证安全性. 掌握云服务器的利用. 3. 利用数据库数据,设计对用户适合的数据服务. 4. 指导数据库日常运行维护. 请问大家我该从哪里开始做起? 平台是 .NET + MS SQL Server 解决方案