从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-12-19 18:34:56

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

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

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

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

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

数据建模学习笔记-1-《高质量数据库建模 1-重大意义》

https://edu.hellobi.com/course/54 <高质量数据库建模 1-重大意义> 1.数据模型的概念和意义 DIKW —— 数据(Data) 信息(Information) 知识(Knowledge) 智慧(Wisdom) 如图所示,我理解,通过数据得到信息,通过信息得到知识,通过知识产生智慧.  最终的目的是产生智慧产生决策 2.什么是数据模型? 数据模型是将数据元素以标准化的模式组织起来,用来模拟现实世界的信息框架蓝图 3.数据模型的要求: 1).直观地模拟世界 2)

《需求工程-软件建模与分析》读书笔记2

随着学习的进行,我的阅读也在继续,在第一阶段读完<需求工程——软件建模与分析>的第一部分和第二部分后,在这几天里我阅读了这本书的第三部分需求分析,这部分是这本书的重点所在,同样在这部分的阅读中对我的帮助也是最大的. <需求工程——软件建模与分析>在需求分析这一部分分别介绍了需求分析概述.过程建模.数据建模.面向对象建模等知识,在阅读中我对这些知识做了简要的了解,在阅读中对我的帮助很大,了解到在软件工程中,需求分析指的是在建立一个新的或改变一个现存的电脑系统时描写新系统的目的.范围.

《需求工程——软件建模与分析》读后感之三

常见的需求分析技术有上下文图.数据流图.实体联系图.功能实体矩阵.功能分解图.过程依赖图.用例图.类图.交互图.活动图.对方约束语言.微规格说明.数据字典.状态转换图等. 之前我们已经学过上下文图了.它的主要作用是描述系统与环境中外部实体之间的界限和联系.他从现实世界的角度说明了系统的边界和环境,并确定了所有的输入和输出.数据流图也是经常用到,它从数据传递和加工的角度,描述了系统从输入到输出的功能处理过程.运用功能分解的方法,用层次结构简化处理复杂的问题.像用例图.类图.交互图等,在以前学习UM

3DSMAX建模技术分析

在游戏美术中,模型的建立是很重要的,今天卡拉小编与大家分享的是3DSMAX建模技术分析1 旋转建模 用于具有中心对称的物体的造型比较简单,只需用工 具画出对称截面,加入(旋转)修改器就可以得到三维实体,有些还可以加入一些特殊变形,以增强表现的效果.2 Loft放样建模 这种建模的含义是将一个二维的交错物件转换为完整的三维物件的一个处理过程,也就是将一个二维的造型物件沿着第三轴向构建出复杂曲面的模型.也可以这样理解这个过程:将许多2D(shape)造型物件紧密且整齐地排列于一条路经(path)上,

《需求工程-软件建模与分析》读书笔记1

新学期开始,新的阅读也随着开展起来.在老师众多推荐书籍中我选择了这本<需求工程-软件建模与分析>.首先,作为我们的教材这本书应该对软件需求有着详细的介绍,并且能作为教材相信它的知识理论有一定的体系结构,对我来说阅读刚好适合.在初学阶段我对软件需求工程并不是很了解,可以说是白纸一张,阅读这本书刚好可以丰富我的基础理论知识.同时作为教材我手中刚好有这本书,阅读起来也是十分方便.总之,在众多的原因下我开始了对这本书的研究. <需求工程-软件建模与分析>这本书分为五部分,这一阶段我主要浏览

03《需求模式——软件建模与分析》

需求分析就是用来解决需求获取得到的信息和需求开发应该建立的软件系统解决方案之间有着很大的差距的需求工程活动.需求分析在需求工程中占据着重要的位置.在需求分析的过程中,需要我们不断地整理.分析,注重每一个细节,确定好系统的边界.建立好模型,如果遇到新的问题,要及时整理解决记录.综合运用需求分析技术.对每一种技术不仅需要广泛阅读,而且需要进行很多的实践.没有哪种需求分析技术能够单独完成对复杂问题的建模任务,只有通过多种需求分析技术的有机结合与集成才能充分的描述复杂的应用.书中对需求分析技术的具体介绍

《需求工程——软件建模与分析》读书笔记二

前一阵子,继续读了<需求工程——软件建模与分析>.之前了解了需求工程的概论和需求的捕获,分别讲了需求捕获中的困难.获取信息的方法和来源:学会分析项目的前景:确定系统边界:涉众分析与硬数据采集:在需求捕获时的方法:面谈,问卷调查,头脑风暴,原型,观察与文档审查: 这次读的主要是介绍需求分析和需求文档化和验证.其中介绍了需求分析技术,需求分析方法等:建模(过程建模,数据建模,面向对象建模):需求规格说明:需求验证. 经过这次读书,我发现需求获取的根本任务是:1.建立分析模型,达成开发者和用户对需求