文檔版本管理系統 數據表設計

作者: DBFocus  來源: 博客園  發布時間: 2010-09-14 15:05  閱讀: 3390 次  推薦: 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
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()