您的位置:知识库 » 软件设计

文档版本管理系统 数据表设计

作者: DBFocus  来源: 博客园  发布时间: 2010-09-14 15:05  阅读: 4379 次  推荐: 0   原文链接   [收藏]  
摘要:本文介绍了文档管理版本管理系统的数据库表设计,大家可以借鉴一下他们的经验。

  最近一个朋友接手了一个项目,为自己部门开发文档版本管理系统。我在和他闲聊中,听他说起数据表设计时遇到的一个疑惑。听他说完后感觉这样的问题还是有一些普遍性的,在这里进行一下分享。

  问题描述

  文档版本管理最主要的是要维护文档的版本链。这很容易让人想到链表这种数据结构,所以我的那位朋友很快就给出了如下的表结构:

create table Table_Docunment
(
	Docunment_Id int not null identity(1000, 1) primary key,
	Docunment_Name nvarchar(64) not null,
	Docunment_SubmitDate datetime not null,
	Docunment_PreId int not null default(-1),
	Docunment_NxtId int not null default(-1),
	.......
);

  其中Docunment_PreId存放前一版本文档的Id,Docunment_NxtId存放下一版本文档的Id。

  起初还没有感觉出什么问题,但当他试图向Table_Docunment填充测试数据或试图写一个存储过程来获取其链上最新的文档时,感觉非常痛苦。

  在他的存储过程中需要进行循环,他自己知道这样性能不好,但又不清楚如何解决。

  解决方案

  问题的关键在于初始的设计并不适合系统的使用场景。原始的设计导致了取某文档的下一版本或上一版本都需要进行连接(Join)操作,若要获得最新版本文档,还需要进行循环。

对原始设计进行修改,新的表结构如下(省略了部分字段):

create table Table_Docunment
(
	Docunment_Id int not null identity(1000, 1) primary key,
	Docunment_Name nvarchar(64) not null,
	Docunment_ChainId int not null default(-1),
	Docunment_VersionId int not null default(-1),
	Docunment_SubmitDate datetime not null,
	......
);

  其中Docunment_ChainId为当前文档所属的版本链,Docunment_VersionId为当前文档在版本链中的版本号(从1开始的连续编号)。

  在列(Docunment_ChainId, Docunment_VersionId)上可加unique约束。

  举例来说,有如下两条文档链:

(图1)

  其中文档上方的两个数字分别代表文档编号(Docunment_Id)和版本编号(Docunment_VersionId)。把这些信息存储到新的Table_Docunment中,结果如下:

Docunment_Id Docunment_Name Docunment_ChainId Docunment_VersionId Docunment_SubmitDate ……
1000 aaa 1 1 2010-01-01 12:03:00 ……
1001 bbb 1 2 2010-01-02 06:02:00 ……
1002 ccc 2 1 …… ……
1003 …… 1 3 …… ……
1004 …… 1 4 …… ……
1005 …… 2 2 …… ……
1006 …… 2 3 …… ……

  对于给定的一个文档,要找其上一版本或下一版本的文档时,只要找其同一条链上版本号小1或大1的文档。若要找最新版本文档只要在链上对版本号取max就行了,也很方便。

  新的需求

  这样的设计已基本满足我那位朋友的需求了,但在某些使用场景下,情况可能会更复杂些。

  若文档链有文档归并的情况,即两个文档链的最新文档版本是同一个文档,示意图如下:

(图2)

  对于这个新的需求,先前的设计就会有一些问题,对于图中文档1007,其版本号对于链1应为5,对于链2应为4,实在是没办法填,我先用了一个问号。

  新的需求改变了链和文档之间的关系。原先链和文档之间为1对多关系(注:标准情况下1对多关系会有两张表,但由于链在此系统中是一个虚概念,而且链实体也只会包含一个Id列,所以在先前设计中省去),现在链和文档之间变为多对多关系。多对多关系需要3张表,两个实体表,一个关系表。在此系统中链的实体表可以省去,所以我们只要引入一张关系表。

  重构原先设计,脚本如下:

create table Table_Docunment
(
	Docunment_Id int not null identity(1000, 1) primary key,
	Docunment_Name nvarchar(64) not null,
	......
);

create table Table_DocChain
(
	DocChain_ChainId int not null,
	DocChain_VersionId int not null default(1) check(DocChain_VersionId >= 1),
	Docunment_Id int not null references Table_Docunment(Docunment_Id),
	DocChain_SubmitDate datetime not null,
	primary key(DocChain_ChainId, DocChain_VersionId)
);

  主要是添加了Table_DocChain这张关系表,对于我在此表上加的约束大家可以自己思考。

  检验一下重构后的设计,把图2中的信息存入新的表结构中。

  Table_Docunment:

Docunment_Id Docunment_Name ……
1000 aaa ……
1001 bbb ……
1002 ccc ……
1003 …… ……
1004 …… ……
1005 …… ……
1006 …… ……
1007 …… ……

 

  Table_DocChain:

DocChain_ChainId DocChain_VersionId Docunment_Id DocChain_SubmitDate
1 1 1000 2010-01-01 12:03:00
1 2 1001 2010-01-02 06:02:00
2 1 1002 ……
1 3 1003 ……
1 4 1004 ……
1 5 1007 ……
2 2 1005 ……
2 3 1006 ……
2 4 1007 ……

  其中关键的两行记录已用粗体标出。

  反过来思考

  前一节讨论了文档归并的情况。有文档归并,就有可能出现文档分支,那该如何处理呢?是否需要修改设计?

  我们先看一下文档分支的示意图:

(图3)

  文档分支没有改变链和文档之间的关系,所以我自己觉得前面的表结构设计不需要修改。

  那图3中分支链上的问号处如何填呢?

  当文档进行分支时,其已经不归属于原先的链了,应新创建一条链。图3中,当文档1005分支时,在表Table_DocChain中应插入一条DocChain_ChainId:3, DocChain_VersionId: 1, Docunment_Id: 1005的记录,此分支的随后文档都归属此新链,这样问题就解决了。

  防止文档链成环

  对于文档链的一个重要约束是不能成环。这个约束可以在应用程序端实现,但数据库端的检查永远是我们最后的一道防线。我们应尽可能通过约束或其他手段来避免错误数据进入数据库。

如果能用check约束来避免链成环是最为直接的,在Table_DocChain中加如下约束:

alter table Table_DocChain
add constraint CK_LoopChain 
check(not exists
		(select
			*
		from
			Table_DocChain DC1
			inner join
			Table_DocChain DC2
			on
				DC1.DocChain_ChainId = DC2.DocChain_ChainId
				and
				DC1.DocChain_VersionId <> DC2.DocChain_VersionId
				and
				DC1.Docunment_Id = DC2.Docunment_Id
			)
);

  其逻辑是在同一条链中,不存在版本号不同,且文档号相同的记录。

  但非常可惜无论在SQL Server 2008还是Oracle中,check约束都不允许使用子查询(Subqueries)。

  我们可以通过带有with check option的视图来达到目的,代码如下:

create view View_DocChain
as
select
	DC1.DocChain_ChainId,
	DC1.DocChain_VersionId,
	DC1.Docunment_Id,
	DC1.DocChain_SubmitDate
from
	Table_DocChain DC1
where
	not exists
	(select
		*
	from
		Table_DocChain DC2
	where
		DC1.DocChain_ChainId = DC2.DocChain_ChainId
		and
		DC1.DocChain_VersionId <> DC2.DocChain_VersionId
		and
		DC1.Docunment_Id = DC2.Docunment_Id	
	)
with check option;

  对于Table_DocChain的插入、修改,都通过View_DocChain来进行,就能防止文档链成环的发生。

0
0

软件设计热门文章

    软件设计最新文章

      最新新闻

        热门新闻