下面看看如何自行設計一套比較靈活的多用戶管理模塊,即該數據庫管理軟件的系統管理員可以自行添加新用戶,修改已有用戶的權限,刪除已有用戶。首先,分析用戶需求,列出該數據庫管理軟件所有需要實現的功能;然后,根據一定的聯系對這些功能進行分類,即把某類用戶需使用的功能歸為一類;最后開始建表:
功能表(function_table)
名稱 類型 約束條件 說明
f_id int 無重復 功能標識,主鍵
f_name char(20) 不允許為空 功能名稱,不允許重復
f_desc char(50) 允許為空 功能描述
用戶組表(user_group)
名稱 類型 約束條件 說明
group_id int 無重復 用戶組標識,主鍵
group_name char(20) 不允許為空 用戶組名稱
group_power char(100) 不允許為空 用戶組權限表,內容為功能表f_id的集合
用戶表(user_table)
名稱 類型 約束條件 說明
user_id int 無重復 用戶標識,主鍵
user_name char(20) 無重復 用戶名
user_pwd char(20) 不允許為空 用戶密碼
user_type int 不允許為空 所屬用戶組標識,和user_group.group_id關聯
采用這種用戶組的架構設計,當需要添加新用戶時,只需指定新用戶所屬的用戶組;當以后系統需要添加新功能或對舊有功能權限進行修改時,只用操作功能表和用戶組表的記錄,原有用戶的功能即可相應隨之變化。當然,這種架構設計把數據庫管理軟件的功能判定移到了前臺,使得前臺開發相對復雜一些。但是,當用戶數較大(10人以上),或日后軟件升級的概率較大時,這個代價是值得的。
四、簡潔的批量m:n設計
碰到m:n的關系,一般都是建立3個表,m一個,n一個,m:n一個。但是,m:n有時會遇到批量處理的情況,例如到圖書館借書,一般都是允許用戶同時借閱n本書,如果要求按批查詢借閱記錄,即列出某個用戶某次借閱的所有書籍,該如何設計呢?讓我們建好必須的3個表先:
書籍表(book_table)
名稱 類型 約束條件 說明
book_id int 無重復 書籍標識,主鍵
book_no char(20) 無重復 書籍編號
book_name char(100) 不允許為空 書籍名稱
……
借閱用戶表(renter_table)
名稱 類型 約束條件 說明
renter_id int 無重復 用戶標識,主鍵
renter_name char(20) 不允許為空 用戶姓名
……
借閱記錄表(rent_log)
名稱 類型 約束條件 說明
rent_id int 無重復 借閱記錄標識,主鍵
r_id int 不允許為空 用戶標識,和renter_table.renter_id關聯
b_id int 不允許為空 書籍標識,和book_table.book_id關聯
rent_date datetime 不允許為空 借閱時間
……
為了實現按批查詢借閱記錄,我們可以再建一個表來保存批量借閱的信息,例如:
批量借閱表(batch_rent)
名稱 類型 約束條件 說明
batch_id int 無重復 批量借閱標識,主鍵
batch_no int 不允許為空 批量借閱編號,同一批借閱的batch_no相同
rent_id int 不允許為空 借閱記錄標識,和rent_log.rent_id關聯
batch_date datetime 不允許為空 批量借閱時間
這樣的設計好嗎?我們來看看為了列出某個用戶某次借閱的所有書籍,需要如何查詢?首先檢索批量借閱表(batch_rent),把符合條件的的所有記錄的rent_id字段的數據保存起來,再用這些數據作為查詢條件帶入到借閱記錄表(rent_log)中去查詢。那么,有沒有什么辦法改進呢?下面給出一種簡潔的批量設計方案,不需添加新表,只需修改一下借閱記錄表(rent_log)即可。修改后的記錄表(rent_log)如下:
借閱記錄表(rent_log)
名稱 類型 約束條件 說明
rent_id int 無重復 借閱記錄標識,主鍵
r_id int 不允許為空 用戶標識,和renter_table.renter_id關聯
b_id int 不允許為空 書籍標識,和book_table.book_id關聯
batch_no int 不允許為空 批量借閱編號,同一批借閱的batch_no相同
rent_date datetime 不允許為空 借閱時間
……
其中,同一次借閱的batch_no和該批第一條入庫的rent_id相同。舉例:假設當前最大rent_id是64,接著某用戶一次借閱了3本書,則批量插入的3條借閱記錄的batch_no都是65。之后另外一個用戶租了一套碟,再插入出租記錄的rent_id是68。采用這種設計,查詢批量借閱的信息時,只需使用一條標準t_sql的嵌套查詢即可。當然,這種設計不符合3nf,但是和上面標準的3nf設計比起來,哪一種更好呢?答案就不用我說了吧。
五、冗余數據的取舍
上篇的“樹型關系的數據表”中保留了一個冗余字段,這里的例子更進一步——添加了一個冗余表。先看看例子:我原先所在的公司為了解決員工的工作餐,和附近的一家小餐館聯系,每天吃飯記賬,費用按人數平攤,月底由公司現金結算,每個人每個月的工作餐費從工資中扣除。當然,每天吃飯的人員和人數都不是固定的,而且,由于每頓工作餐的所點的菜色不同,每頓的花費也不相同。例如,星期一中餐5人花費40元,晚餐2人花費20,星期二中餐6人花費36元,晚餐3人花費18元。為了方便計算每個人每個月的工作餐費,我寫了一個簡陋的就餐記賬管理程序,數據庫里有3個表:
員工表(clerk_table)
名稱 類型 約束條件 說明
clerk_id int 無重復 員工標識,主鍵
clerk_name char(10) 不允許為空 員工姓名
每餐總表(eatdata1)
名稱 類型 約束條件 說明
totle_id int 無重復 每餐總表標識,主鍵
persons char(100) 不允許為空 就餐員工的員工標識集合
eat_date datetime 不允許為空 就餐日期
eat_type char(1) 不允許為空 就餐類型,用來區分中、晚餐
totle_price money 不允許為空 每餐總花費
persons_num int 不允許為空 就餐人數
就餐計費細表(eatdata2)
名稱 類型 約束條件 說明
id int 無重復 就餐計費細表標識,主鍵
t_id int 不允許為空 每餐總表標識,和eatdata1.totle_id關聯
c_id int 不允許為空 員工標識標識,和clerk_table.clerk_id關聯
price money 不允許為空 每人每餐花費
其中,就餐計費細表(eatdata2)的記錄就是把每餐總表(eatdata1)的一條記錄按就餐員工平攤拆開,是個不折不扣的冗余表。當然,也可以把每餐總表(eatdata1)的部分字段合并到就餐計費細表(eatdata2)中,這樣每餐總表(eatdata1)就成了冗余表,不過這樣所設計出來的就餐計費細表重復數據更多,相比來說還是上面的方案好些。但是,就是就餐計費細表(eatdata2)這個冗余表,在做每月每人餐費統計的時候,大大簡化了編程的復雜度,只用類似這么一條查詢語句即可統計出每人每月的寄餐次數和餐費總帳:
select clerk_name as personname,count(c_id) as eattimes,sum(price) as ptprice from eatdata2 join clerk_tabsle on (c_id=clerk_id) join eatdata1 on (totleid=tid) where eat_date>=convert(datetime,'"&the_date&"') and eat_date<dateadd(month,1,convert(datetime,'"&the_date&"')) group by c_id
想象一下,如果不用這個冗余表,每次統計每人每月的餐費總帳時會多麻煩,程序效率也夠嗆。那么,到底什么時候可以增加一定的冗余數據呢?我認為有2個原則:
1、用戶的整體需求。當用戶更多的關注于,對數據庫的規范記錄按一定的算法進行處理后,再列出的數據。如果該算法可以直接利用后臺數據庫系統的內嵌函數來完成,此時可以適當的增加冗余字段,甚至冗余表來保存這些經過算法處理后的數據。要知道,對于大批量數據的查詢,修改或刪除,后臺數據庫系統的效率遠遠高于我們自己編寫的代碼。
?。病⒑喕_發的復雜度?,F代軟件開發,實現同樣的功能,方法有很多。盡管不必要求程序員精通絕大部分的開發工具和平臺,但是還是需要了解哪種方法搭配哪種開發工具的程序更簡潔,效率更高一些。冗余數據的本質就是用空間換時間,尤其是目前硬件的發展遠遠高于軟件,所以適當的冗余是可以接受的。不過我還是在最后再強調一下:不要過多的依賴平臺和開發工具的特性來簡化開發,這個度要是沒把握好的話,后期維護升級會栽大跟頭的。
新聞熱點
疑難解答