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

在业务需求中,经常需要我们在系统中能够记录历史信息,能够查看到历史变动情况,这时我们可以通过增加开始结束时间字段来记录数据的历史版本。对数据的历史记录主要分为:关系、属性历史,实体历史和变更历史。

关系、属性历史记录

所谓关系历史记录就是指两个实体之间的关系存在历史版本。比如部门表和员工表,对于某一个时刻来说,一个部门有多个员工,一个员工只属于一个部门,所以是个一对多的关系。而我们希望把这个关系记录下历史变动,那么就会形成多对多关系。多对多关系就形成中间表,然后我们在中间表上加入“开始时间”字段和“结束时间”字段即可记录这个关系的历史。

对某个实体的属性记录历史记录会形成一对多的关系表,比如产品价格属性,我们希望把所有历史定价都记录下来,那么就会形成产品和价格一对多的关系。

在AdventureWorks数据库中,我们可以看到大量的这种记录关系历史的设计。比如:

员工、部门、轮班的历史记录:

这就是前面提到的一对多关系因为记录历史变为多对多关系的例子。

产品对成本和售价的历史记录:

这就是典型的属性历史记录,对于产品的众多属性,我们之关系成本和售价这两个属性的历史,所有可以建立一对多关系的价格历史表。

销售和区域以及销售配额的历史记录:

区域和销售本来也是普通的一对多关系,一个销售属于某个片区,一个区域对应多个销售。现在由于历史记录,所以形成多对多的关系表SalesTerritoryHistory。而对于销售配额,因为是记录到季度的,一季度只有一个销售配额,所以不需要开始时间和结束时间,只需要一个季度第一天即可(结束时间是可以根据这个季度的第一天而计算出来的,所以不需要再存储)。

区域与销售人员的关系在增加了中间表形成多对多后,仍然保留了原来的一对多关系,从数据上来看不是这样的,因为两个表的数据是不一致的,所以我推断这是另外一个一对多关系,而不是原来的区域和销售的分配对应关系表。

小结:

当需要对关系或属性记录历史时,会把关系提升一个复杂度,也就是说原来是一对一的,现在会变成一对多,原来是一对多的,现在会变成多对多。在历史记录表中增加“开始时间”和“结束时间”两个字段来表示该行数据的时间有效性。AdventureWorks数据库中使用了NULL值设为“结束时间”来表明这条数据是当前有效的,但是笔者并不推荐这么做,最好是把两个字段都设置为NOT NULL,在比较时可以得到统一的查询语句:

where @d between StartDate and EndDate

另外SalesTerritoryHistory这个表只记录“开始时间”而不记录“结束时间”这也是一个不好的设计,虽然结束时间是可以计算出来的,但是每次查询的时候还需要去计算结束时间,真不是一个好方法。最好是把两个字段都保留,用户只需要输入开始时间,由前端程序去初始化结束时间,然后一并保存。

实体历史记录

主实体历史记录

实体的历史记录是指对一个实体数据的任何更改,都把整条数据都产生一条新记录,而不是只针对某个属性或者关系。对实体进行历史记录,我们也可以采用添加开始时间结束时间的方式,但是更多的时候我们对整个实体记录历史并不是为了随时查询历史上某个时间点这个实体的值,而是为了记录一个“版本Version”信息,方便在审计某个实体的变更时对比。如果我们是出于审计的需要而记录的历史版本,那么这些历史数据平时是不会参与到业务查询中的,所以并不需要记录开始时间,结束时间,取而代之的,我们可以增加“版本”字段,当然还有审计用到的“最后更新时间”和“最后更新人”,

这样就实体的变化情况,如果我们仅仅是增加Version字段,在查询当前版本时会很麻烦,因为我们必须拿到最高的那个版本号,然后才能把这个最新版本的记录作为当前记录,为了优化这个性能问题,我们一般还需要再添加布尔型的“是否当前版本IsCurrent”字段来标识当前版本。增加了这个字段后,那么在更改实体数据时就会更麻烦一些。首先需要将老数据版本号获得,+1生成新的版本号,然后将老数据的“是否当前版本”字段置为0,更新老数据的“最后更新时间”和“最后更新人”,然后插入新版本号的数据,而且新版本是当前版本。我在AdventureWorks数据库中并没有看到关于实体的历史记录的设计,不过我们可以看SharePoint的数据库设计,就是采用我这里提到的版本设计的方法。有兴趣的可以查看一下SharePoint的ContentDB的AllUserData表,tp_Version就是记录版本的,tp_IsCurrent和tp_IsCurrentVersion就是标记当前版本的。

附属实体的历史记录

在进行实体历史记录时,还面临的一个问题是,附属的子实体是否也需要一并进行历史记录。比如我们要对采购订单这么一个实体进行历史记录,每次对采购订单的修改都会生成一个新版本的采购订单。如果一个采购订单下面有100条采购明细,那么我们在编辑了采购订单主表后,创建了新版本的采购主表数据,是否对这100条明细也创建对应的新版本数据呢?如果创建,那么采购明细表的数据量就会飞涨,而且实际上我们这里并没有编辑这100条明细,新版本的明细数据是一模一样的,如果不创建,那么怎么保持这种外键约束呢?毕竟明细表上面的外键对应的可是老版本的采购订单的ID啊!

其实两种方案都可以,第一种方案开发简单,如果明细并不是那么多,或者本身单据的数据量并不大,那么重复一点明细表并不会带来太大的影响。第二种方案开发会很复杂,需要新老数据逐条对比,找到差异,如果主表有更改,那么为主表创建新版本,如果100条明细中有2条更改,那么就为这2条创建新版本。

下面详细说一下采用第二种的解决方案的模型设计。首先,我们需要断开主表和附属表的外键,将Form和Item作为两个独立的实体,各自添加“版本”,“是否当前版本”等属性。为Form添加业务主键“FormNumber”,用于唯一标识一个表单(由于版本记录的原因,所以FormNumber不是Form的主键),然后在Item表中添加“FormNumber”,用于标识这些Item是属于哪个表单。

select *
from Form 
where IsCurrent=1 and IsDeleted=0 and FormNumber=@formNumber;
select *
from Item 
where IsCurrent=1 and IsDeleted=0 and FormNumber=@formNumber;

变更历史记录

无论前面讲到的对关系,属性还是整个实体的历史记录,都会在业务表中形成新的数据,数据的增加一方面会导致查询的效率变低,另一方面也使得每次查询时都需要带上额外的查询条件,非常不方便。于是我们想到了另一种保存历史记录的方式,那就是我们像记录日志一样,把变更了的部分记录到日志表中。

记录变更日志的好处是不影响现有数据库模型的设计,也就是说所有实体和关系都不需要改,我们只需要增加一个变更日志表即可。但是变更日志一般是前端程序通过对比前后记录,找到变更的属性,然后写入的,并不是数据库做的事。坏处也显而易见,那就是还原历史数据不方便,不能像前面的模型那样可以快速的查询数据的历史状态。

所以变更日志表这种处理方式只用于审计的需求,而不能用于业务上要对历史数据的查询需求。在AdventureWorks数据库中有一个TransactionHistory表,用于记录各个订单事务的,虽然不是记录订单变更的,但是也有和变更历史记录类似的结构。

历史数据查询优化

前面提到由于保留历史数据的原因,所以会将数据库中对应表的数据量增加很多倍,数据量的增加必然导致查询变慢,所以我们在记录历史数据后很有必要对表进行查询优化。优化可以采用以下解决方案:

归档表

如果我们的历史数据在平时的业务中并不需要,只有在特殊场景才会用到历史数据表,那么我们可以将历史数据表建立一模一样结构的归档表,然后定时将业务系统中的历史数据转移到归档表中。当然,前端软件系统也要做对应的修改,对于老的历史数据需要查询归档表,而新的数据是查询当前表。在AdventureWorks只对TransactionHistory就建立了对应的归档表。

分区

建立分区比归档表的好处是在物理上,老数据和新数据可以存储在不同的地方,新老数据可以各自建立各自的索引树,而在逻辑上对程序来说仍然是访问一个表,前端程序不需要做什么修改。比如对于开始结束日期的历史数据记录方式,我们可以把结束日期为9999-12-31的数据(当前有效数据)分到一个区,剩下的分到另一个区。对于版本记录的方式,我们可以将“是当前版本”分到一个区,把其他的数据分到另一个区。

分区后在更新数据时会导致老数据的区块转移,因为老数据本来是在Current区块的,现在由于更改了实体,老数据需要转移到Old区块,然后将新数据插入到Current区块,除了分区的移动还有对应的索引的变动,所以更新数据时会相对慢一些。

索引

如果对于Oracle数据库,那么我们可以对IsCurrentVersion字段建立位图索引,如果是SQL Server这种不支持位图索引的数据库,那么我们也可以在建立B树索引时把IsCurrentVersion放在第一列,因为这个列是必然放入过滤条件的。

时间: 2024-08-24 20:20:19

从AdventureWorks学习数据库建模——保留历史数据的相关文章

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

最近打算写写数据库建模的文章,所以打算分析微软官方提供的SQL Server示例数据库AdventureWorks,看看这个数据库中有哪些值得学习的地方. 首先我们需要下载安装一个SQL Server数据库引擎,然后下载示例数据库,这里笔者用的是SQL2008R2,所以下载的是AdventureWorks2008R2,下载地址: http://msftdbprodsamples.codeplex.com/ 下载数据库后附加到SQL Server中即可看到这个数据库. 这是一个自行车制造和销售公司

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

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

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

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

使用PowerDesigner进行数据库建模入门

阅读目录 两种重要模型 创建表和主外键 创建视图和存储过程 生成数据库 PowerDesigner(简称PD)是一种强大的数据库建模工具,使用PD可以创建业务模型,UML类图等,当然最主要的功能是数据库建模.我打算分以下几个部分来讲如何使用PD. 1,两种重要模型,概念模型和物理模型 2,创建表和主外键 3,创建视图和存储过程 4,生成数据库 回到顶部 两种重要模型 首先说概念模型,概念模型是一个抽象的宏观层次的业务模型,比如E-R(实体关系)图,在概念模型中最重要的对象是实体和关系. 根据概念

辛星让mysql跑的更快第一节之优化的方向和数据库建模

最近计划写一套书目,也就是关于mysql的优化的,那么首先在博客上写写,然后整理成pdf的文档的形式,当然也期待各位的关注了.对于mysql的优化是一个比较大的话题,可优化的地方也很多,大致想了一下,可以从这些地方下手. 首先就是硬件层次,包括选择合适的操作系统.选择合适的硬件,然后就是源码层次,不过虽然mysql是开源的,但是能够修改其源代码的公司虽然不少,但是也没有那么多,但是我们可以选择更加合适的编译器重新编译其源代码,然后就是设计到表的设计,也就数据库建模. 其次可以考虑使用一些其他技术

数据库建模模板、菜单显示出问题解决方案

数据库建模模板.菜单显示出问题解决方案 您使用的是哪个版本? 要查找您使用的 Visio 版本,请在"帮助"菜单上单击"关于 Microsoft Office Visio".版本的名称会显示在对话框最上面的文本行中. Standard     此版本不包含"数据库模型图"模板. Professional     此版本支持"数据库模型图"模板的反向工程功能(即在 Visio 中使用现有数据库创建模型),但是不支持正向工程功能(

HSQLDB源码学习——数据库安装启动及JDBC连接

HSQLDB 是一个轻量级的纯Java开发的开放源代码的关系数据库系统.因为HSQLDB的轻量(占用空间小),使用简单,支持内存运行方式等特点,HSQLDB被广泛用于开发环境和某些中小型系统中. 在http://sourceforge.net/projects/hsqldb/files/下载了HSQLDB 1.8.0版本.把下载的zip文件解压缩至任意目录例如c:\hsqldb1.8便完成安装. hsqldb有四种运行模式: 一.内存(Memory-Only)模式:所有数据都在内存里操作.应用程

新手学习数据库(一)用Powerdesigner设计数据库

说明: 一.学会用开发语言进行数据库编程,其关键是在于学会sql语言,开发语言只不过给程序员提供了一个操作数据库的接口罢了. 二. 本人也是初学者,采用的数据库设计软件是powerdesigner.利用该软件用户可以设计E-R图,然后软件会自动根据E-R图生成创建数据库表,即表间约束的sql语句. 三.由于powerdesigner安装软件大于220M,无法上传,所以请使用者自己百度下载. 四.学习数据库的数据.本人用的是微软的sql2000.sql语句的参考书籍是<SQL Server 200

W 3 School学习数据库

W3 School 学习数据库地址: http://www.jb51.net/w3school/sql/sql_select.htm 一,SQL语法 SQL DML 和 DDL 可以把 SQL 分为两个部分:数据操作语言 (DML) 和 数据定义语言 (DDL). SQL (结构化查询语言)是用于执行查询的语法.但是 SQL 语言也包含用于更新.插入和删除记录的语法. 查询和更新指令构成了 SQL 的 DML 部分: SELECT - 从数据库表中获取数据 UPDATE - 更新数据库表中的数据