时间:2021-07-01 10:21:17 帮助过:2人阅读
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是多对多关系,在客户下单的时候让用户复选购买原因,是因为促销,还是看了杂志广告之类的,简单多对多关系,这个没啥好说的。
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在一起时,联想到的就是采购订单:
和销售订单类似,采购订单也 分为PurchaseOrderHeader和PurchaseOrderDetail。
?
系统中先使用ProductVendor定义了哪些Vendor能供应哪些产品,在生成采购订单时会基于这里面的内容来生成,但是在模型上并不直接反应,因为Product属于Detail表,而Vendor是属于Header表,不能像前面说到的SpecialOfferProduct一样通过引用来传递这种限制。
Header记录的是采购人员Employee与供应商Vendor的关系。一个采购订单Header中会包含多个明细Detail,里面记录了采购哪些Product。采购订单比销售订单简单很多,最为买方,不会去记录促销,购买原因之类的信息。另外采购中没有涉及到币种汇率问题,我估计这是因为产品都在国内采购和结算,所以只有一种币种,而销售是面向世界各地,所以涉及到币种汇率。
除了前面说到的销售订单和采购订单外,在生产过程中还有生产订单,用于表示产品的生产情况。主要有WorkOrder和WorkOrderRouting两个实体。
?
WorkOrder记录了生产某个产品的数量、报废和时间情况,而WorkOrderRouting记录的是在某个产品的具体生产过程中有哪些工序,每个工序的时间、成本等情况。总的来说,这是一个非常非常简化的生产工作订单模型。
除了前面说到的实体外,还有其他几个独立出来的实体需要说明一下:
另外还有一个其归档表TransactionHistoryArchive,其结构和TransactionHistory一模一样,这里面记录的是生产工作订单或者采购订单或者销售订单这3个事务的产品、日期,数量等公共信息。这个表可以认为是一个事务的日志表,平时并不参与各个实体的查询,只有在审计或者跟踪数据变化时才用到。
TransactionHistory表中的数据是在各个Order表上建立Trigger自动插入进去的,而不是由外部程序代码去控制。由于本身事务表的数据量就比较大,而这个表却存了三个事务表中的数据,所以增长特别快,必须进行归档操作,把老数据搬移到另一个归档表中,这样才能保证查询新TransactionHistory表的速度。
这是一个记录当前数据库定义创建时数据库的版本也可以定义当前数据库定义脚本的版本。对于通用的产品来说,这个表比较重要,因为产品可能需要升级,升级程序在升级前读取这个表,知道了当前数据库定义是什么个版本,然后就可以查询到将当前版本的数据库升级到新版的数据库所需要修改的SQL,然后执行这些SQL。
而应用程序在运行时第一件事就是检查这个表中的版本信息,保证数据库定义的版本与程序要求的版本匹配,这样程序才能正常运行。
对于企业内部系统,一般只有一个实例,而且由企业内部的IT人员开发维护,所以这个表没有也没什么问题。
这是记录数据库DDL(数据定义语言,比如CREATE, ALTER, DROP等)操作的日志表。这个表是由Database Trigger自动维护,当在这个数据库中执行了DDL的时候,系统会触发Trigger,往这个表中记录一条数据。这是一个好东西!
?另外还有一些因为范式化抽象出来的码表,我在前面的模型中没有提到,比如CountryRegion,StateProvince等这些都比较简单,就不一一累述了。
这篇文章我只是简单分析了下实体和实体关系,下面一篇文章会进一步分析其中的细节,有哪些优缺点。?
?
?
?
?
?
从AdventureWorks学习数据库建模——实体分析
标签: