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

前言:項目中又要用到一個四級分類數據表,以前我曾經在這方面按步就班玩過很多CRUD的操做,感受工做內容有很多重複,有必要再總結一下,對新手可能會有點幫助,同時以備本身往後再用。html

 

一、數據表設計web

開門見山,Category表設計以下:sql

Category

數據表字段簡單說明:數據庫

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

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

 

二、簡單查詢函數

(1)一般,在實際應用中簡單查詢某一級別可用(Status等於1)的分類很是簡單:spa

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支持):code

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

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語句)。

 

三、項目實踐經驗之談

在實際項目中,對於分類表,一般都會作相應的緩存(這種類型的數據一般說多也很少,說少也很多,可是相對比較穩定),總結一下我在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)、直接查詢操做緩存中的分類數據。

 

四、思考

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

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

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

   … … … …

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

相關文章
相關標籤/搜索