分類數據表設計的簡單再總結


前言:項目中又要用到一個四級分類數據表,之前我曾經在這方面按步就班玩過不少CRUD的操作,感覺工作內容有不少重復,有必要再總結一下,對新手可能會有點幫助,同時以備自己日后再用。

 

1、數據表設計

開門見山,Category表設計如下:

Category

數據表字段簡單說明:

列名 數據類型 默認值 備注
Id int   自增主鍵
Name varchar(256)   分類類別名稱
ParentId int 0 父母分類Id
Depth int 1 深度,從1遞增
Status int 0 狀態:0禁用,1啟用
Priority int 0 優先級,越大,同級顯示的時候越靠前

說明:在設計實現這個數據表之前,我搜索參考並比較了一下其他無限層級設計方案,比如這一篇這一篇,雖然本文最終使用了最常見的層級設計而沒有采納另外的幾種方法,但是不可否認它們對開闊設計思路是很有啟發的。

 

2、簡單查詢

(1)通常,在實際應用中簡單查詢某一級別可用(Status等於1)的分類非常簡單:

1
2
3
4
5
6
7
8
SELECT  [Id]
       ,[ Name ]
       ,[ParentId]
       ,[Depth]
       ,[Status]
       ,[Priority]
   FROM  [Category](NOLOCK)
   WHERE  Status=1 AND  Depth=n --n>=1

最后按照優先級(Priority)字段逆序即可。

(2)當需要按照某一個Id查找它及它的所有子級或者父級成員,避開遞歸,直接寫sql查詢會比較難以下手,而且Sql Server2005之前的版本還需要用到臨時表,處理起來不是那么直觀。自從Sql Server2005/2008橫空出世,利用With語句可用非常輕松地寫出查詢,下面貼兩個開發中經常用到的查詢存儲過程(Sql Server2005/2008支持):

a、按照某一個Id查詢它及它的所有子級成員存儲過程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
CREATE  PROCEDURE  [dbo].[sp_GetChildCategories] (@Id int )
AS
BEGIN
WITH  Record AS (
     SELECT
     Id,
     Name ,
     ParentId,
     Depth,
     Status,
     Priority
FROM
     Category(NOLOCK)
     WHERE  Id=@Id
     UNION  ALL
         SELECT
     a.Id Id,
     a. Name  Name ,
     a.ParentId ParentId,
     a.Depth Depth,
     a.Status Status,
     a.Priority Priority
FROM
     Category(NOLOCK) a JOIN  Record b
     ON  a.ParentId=b.Id
)
 
SELECT
     Id,
     Name ,
     ParentId,
     Depth,
     Status,
     Priority
FROM
     Record
     WHERE  Status=1
     ORDER  BY  Priority DESC
     
END

b、按照某一個Id查詢它及它的所有父級成員存儲過程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
CREATE  PROCEDURE  [dbo].[sp_GetParentCategories] (@Id int )
AS
BEGIN
WITH  Record AS (
     SELECT
     Id,
     Name ,
     ParentId,
     Depth,
     Status,
     Priority
FROM
     Category(NOLOCK)
     WHERE  Id=@Id
     UNION  ALL
     SELECT
     a.Id Id,
     a. Name  Name ,
     a.ParentId ParentId,
     a.Depth Depth,
     a.Status Status,
     a.Priority Priority
FROM
     Category(NOLOCK) a JOIN  Record b
     ON  a.Id=b.ParentId
)
 
SELECT
     Id,
     Name ,
     ParentId,
     Depth,
     Status,
     Priority
FROM
     Record
     WHERE  Status=1
     ORDER  BY  Priority DESC
     
END

分析上面兩個存儲過程,實際上,您也可以提取出下面的兩段sql語句直接代替上面的查詢存儲過程:

c、按照某一個Id查詢它及它的所有子級成員sql語句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
WITH  Record AS (
     SELECT
     Id,
     Name ,
     ParentId,
     Depth,
     Status,
     Priority
FROM
     Category(NOLOCK)
     WHERE  Id=@Id  --@Id是外部傳入的參數
     UNION  ALL
     SELECT
     a.Id Id,
     a. Name  Name ,
     a.ParentId ParentId,
     a.Depth Depth,
     a.Status Status,
     a.Priority Priority
FROM
     Category(NOLOCK) a JOIN  Record b
     ON  a.ParentId=b.Id
)
 
SELECT
     Id,
     Name ,
     ParentId,
     Depth,
     Status,
     Priority
FROM
     Record
     WHERE  Status=1
     ORDER  BY  Priority DESC

 

d、按照某一個Id查詢它及它的所有父級成員sql語句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
WITH  Record AS (
     SELECT
     Id,
     Name ,
     ParentId,
     Depth,
     Status,
     Priority
FROM
     Category(NOLOCK)
     WHERE  Id=@Id  --@Id是外部傳入的參數
     UNION  ALL
     SELECT
     a.Id Id,
     a. Name  Name ,
     a.ParentId ParentId,
     a.Depth Depth,
     a.Status Status,
     a.Priority Priority
FROM
     Category(NOLOCK) a JOIN  Record b
     ON  a.Id=b.ParentId --匹配關系
)
 
SELECT
     Id,
     Name ,
     ParentId,
     Depth,
     Status,
     Priority
FROM
     Record
     WHERE  Status=1
     ORDER  BY  Priority DESC

參數@Id毫無疑問,是你需要在外部程序里傳入的參數。選擇存儲過程或者直接使用sql語句看自己的喜好(個人傾向於寫sql語句)。

 

3、項目實踐經驗之談

在實際項目中,對於分類表,通常都會做相應的緩存(這種類型的數據通常說多也不多,說少也不少,但是相對比較穩定),總結一下我在web項目中的使用經驗(經驗之談,請務必小心甄別取舍):

(1)、一次性取出數據庫中所有可用分類類別數據;

(2)、數據(Category表數據)轉換成對應實體Category;

a、Category實體類

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using  System;
 
/// <summary>
/// 分類實體
/// </summary>
[Serializable]
public  class  Category : BaseCategory //繼承自BaseCategory
{
     public  int  Id { get ; set ; }
 
     public  string  Name { get ; set ; }
 
     public  int  ParentId { get ; set ; }
 
     public  int  Depth { get ; set ; }
 
     public  int  Status { get ; set ; }
 
     public  int  Priority { get ; set ; }
 
}

我們看到,Category實體繼承自BaseCategory類,這個類我們定義如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public  abstract  class  BaseCategory : DotNet.Common.Model.PagerBase //PagerBase 分頁基類
{
     /// <summary>
     /// 一級分類id
     /// </summary>
     public  int  FirstCategoryId { get ; set ; }
     /// <summary>
     /// 一級分類名
     /// </summary>
     public  string  FirstCategoryName { get ; set ; }
 
     /// <summary>
     /// 二級分類id
     /// </summary>
     public  int  SecondCategoryId { get ; set ; }
     /// <summary>
     /// 二級分類名
     /// </summary>
     public  string  SecondCategoryName { get ; set ; }
 
     /// <summary>
     /// 三級分類id
     /// </summary>
     public  int  ThirdCategoryId { get ; set ; }
     /// <summary>
     /// 三級分類名
     /// </summary>
     public  string  ThirdCategoryName { get ; set ; }
 
     /// <summary>
     /// 四級分類id
     /// </summary>
     public  int  ForthCategoryId { get ; set ; }
     /// <summary>
     /// 四級分類名
     /// </summary>
     public  string  ForthCategoryName { get ; set ; }
}

b、接着通過一定的方法或函數,對Category實體類再做一些處理,完善它的層級關系。比如通過遞歸函數,初始化一次,准備好這些有層級的數據實體:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/// <summary>
/// 分類實用幫助類
/// </summary>
public  class  CategoryUtil
{
     /// <summary>
     /// 分層級的數據實體字典 key: Id  value:分類實體
     /// </summary>
     public  static  IDictionary< int , Category> DictCategories { get ; set ; }
 
     static  CategoryUtil()
     {
         Init();
     }
 
     /// <summary>
     /// 根據品類類別構造一個適合查找的dictionary(1~4級品類ID和對應名稱)
     /// </summary>
     private  static  void  Init()
     {
         //DictProductTypes=//查庫,一次取出所有可用分類數據 to do
         foreach  (KeyValuePair< int , Category> kv in  DictCategories)
         {
             Category model = kv.Value;
             switch  (model.Depth)
             {
                 default :
                     break ;
                 case  1:
                     model.FirstCategoryId = model.Id;
                     model.FirstCategoryName = model.Name;
                     break ;
                 case  2:
                     model.SecondCategoryId = model.Id;
                     model.SecondCategoryName = model.Name;
                     break ;
                 case  3:
                     model.ThirdCategoryId = model.Id;
                     model.ThirdCategoryName = model.Name;
                     break ;
                 case  4:
                     model.ForthCategoryId = model.Id;
                     model.ForthCategoryName = model.Name;
                     break ;
 
             }
             InitCascadeCategory(model, model.ParentId, model.Depth);
         }
     }
 
     /// <summary>
     /// 初始化層級
     /// </summary>
     /// <param name="query"></param>
     /// <param name="parentId"></param>
     /// <param name="depth"></param>
     private  static  void  InitCascadeCategory(Category query, int  parentId, int  depth)
     {
         if  (depth < 2)
         {
             return ;
         }
         foreach  (KeyValuePair< int , Category> kv in  DictCategories)
         {
             Category model = kv.Value;
             if  (parentId == model.Id && model.Depth == depth - 1)
             {
                 switch  (depth)
                 {
                     default :
                         break ;
                     case  2:
                         query.FirstCategoryId = model.Id;
                         query.FirstCategoryName = model.Name;
                         break ;
                     case  3:
                         query.SecondCategoryId = model.Id;
                         query.SecondCategoryName = model.Name;
                         break ;
                     case  4:
                         query.ThirdCategoryId = model.Id;
                         query.ThirdCategoryName = model.Name;
                         break ;
                 }
                 InitCascadeCategory(query, model.ParentId, --depth); //遞歸
                 break ;
             }
         }
     }
 
}

然后進行第(3)步,進行緩存。

需要特別說明的是,BaseCategory類我們只多設計了8個屬性,四個層級(目前為止開發中超過四個層級的我還沒有遇到過),當然你可能會問,如果超過4個層級怎么辦?曾經看到過有一種通用設計的思路,就是通過一個集合對象(或嵌套的集合對象)進行層級類別的存取,比如泛型Dictionary,LinkedList等等,我還沒有嘗試實現過,但是設計實現思路確實可以借鑒。

(3)、按照某種策略緩存數據,如每天或者每個月更新一次數據,等等。

(4)、直接查詢操作緩存中的分類數據。

 

4、思考

(1)、數據表中Depth字段是不是必要的,是否多余?

(2)、查詢時如何避免遞歸?

(3)、層級過多(比如超過20層級),有沒有更好的設計和解決方法?

   … … … …

越想越感到問題多多,期待您的建議和意見。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM