树状数据库设计方案思考(原创,如需转载请标明出处)

来源:互联网 发布:linux终端连接工具 编辑:程序博客网 时间:2024/04/29 16:22
 

前言:

在我做的医院肿瘤数据库系统中有很多信息需要用树状,或者层次型的控件显示出来,比如说每个科室的功能信息,所有肿瘤病种的分类等等;在本系统中科室的功能信息是通过treeview控件来显示的,肿瘤病种的分类信息也是通过treeview来显示的,如果在设计数据库的时候就能够很好的将这些相关信息级联起来,那么在处理显示的时候不但会减少工作量而且还会使代码清晰健壮。

 

1.三种设计模式的思考与分析

1.1 adjacency list model(毗邻目录模式)

     这种模式我们经常用到,很多的教程和书中也介绍过。我们通过给每个节点增加一个属性 parent 来表示这个节点的父节点从而将整个树状结构通过平面的表描述出来。在实际的数据库中,你需要用数字的id来标示每个节点,数据库的表结构大概应该像这样:id, parent_id, name, description。有了这样的表我们就可以通过数据库保存整个多级树状结构了。

1.1.2实例运用

在医院肿瘤数据库系统中,由于存在很多与肿瘤相关的科室和一些各个科室需要录入的材料,我的表建立的情况如下:

ID   NAME        PID  ABBRE     URL

1     科室录入    0      KSLR            NULL

2     医生查询    0      YSCX            DoctorQuery.aspx

3     随访情况  0     SFQK           NULL

4     门诊诊断表      0     MZZDB         outdiagnose.aspx

5     肿瘤外科  1     ZLWK          NULL

6     肿瘤内科  1     ZLNK           NULL

7     妇科     1          FK                   NULL

8     放疗科         1          FLK                  NULL

9     高能聚焦科      1     GNJJK        NULL

10    影像科         1      YXK            NULL

11    内窥镜科  1     NKJK            NULL

12    介入科         1      JRK       NULL

13    病理科         1      BLK       NULL

15    B超科          1      BCK  NULL

16    检验科         1      JYK        NULL

17    高能表1       9      GNB1      gnjjbg1.aspx

18    放射治疗小节   8     FSZLXJ          NULL

19    高能表2   9     GNB2           NULL

20    病理表1   13    BLB1    NULL

21    随访录入  3     SFLR            VisitationInput.aspx

22    随访查询  3     SFCX       VisitationQuery.aspx

23    门诊诊断查询   0     MZZDCX   NULL

 

在这里我多加了个URL属性,因为具体到每个输入的具体模块肯定是需要一个单独的页面的。

 

显示过程如下:

//增加第一层节点

 for (int i = 0; i < dt.Rows.Count; i++)

{

    if (dt.Rows[i][2].ToString() == "0")

    {

      TreeNode tdsheng = new TreeNode(dt.Rows[i][1].ToString(),                       dt.Rows[i][0].ToString(), "",     dt.Rows[i][4]. ToString(), "_self");

                    tdroot.ChildNodes.Add(tdsheng);

 

 

                    //增加第二层节点

                    for (int j = 0; j < dt.Rows.Count; j++)

                    {

                        if (dt.Rows[j][2].ToString() == dt.Rows[i][0].ToString())

                        {

                            TreeNode tdshi = new TreeNode(dt.Rows[j][1].ToString(), dt.Rows[j][0].ToString(), "", dt.Rows[j][4].ToString(), "");

                            tdsheng.ChildNodes.Add(tdshi);

                            //增加第三层节点

                            for (int k = 0; k < dt.Rows.Count; k++)

                            {

                                if (dt.Rows[k][2].ToString() == dt.Rows[j][0].ToString())

                                {

                                    TreeNode tdjt = new TreeNode(dt.Rows[k][1].ToString(), dt.Rows[k][0].ToString(), "", dt.Rows[k][4].ToString(), "");

                                    tdshi.ChildNodes.Add(tdjt);

                                }

                            }

                        }

                    }

                }

            }

 

1.1.3 adjacency list model 的优缺点

     优点:显示数据的时候只需要一个递归函数或者简单的嵌套循环,这种方法很简单,容易理解,好上手。另外如果数据库存在扩展性那么也很方便进行扩展,只需要找对父节点就行了,每个节点再理论上也能够有无穷多个子节点。在本系统中因为有些科室可能以后还会有新的信息需要输入,所以考虑到这个扩展性我采用了这种方法来设计这个模块。

     缺点:主要是因为运行速度很慢,由于得到每个节点都需要进行数据库查询,数据量大的时候要进行很多查询才能完成一个树。另外由于要进行递归运算或者嵌套循环,所以系统的开销也比较大。

 

1.2预排序遍历树算法(modified preorder tree traversal algorithm)

1.2.1预排序遍历树算法的简介

为了便于描述这种算法我举一个食物分类表的例子来说明。

假设我要实现下述的食物分类:

Food
|
|
--Fruit
|
  |
|
  |---Red
|
  |  |
|
  |  |--Cherry
|
  |               
|
  |--Yellow               
|
     |
|
     |--Banana
|
|
--Meat
   |       
   |--Beef
   |
   |--Pork

我们可以这样来做:我们首先将多级数据按照下面的方式画在纸上,在根节点Food的左侧写上 1 然后沿着这个树继续向下 Fruit 的左侧写上 2 然后继续前进,沿着整个树的边缘给每一个节点都标上左侧和右侧的数字。最后一个数字是标在Food 右侧的 18 在下面的这张图中你可以看到整个标好了数字的多级结构。

                 1 Food 18
                                    |
                +---------------------------------------+
                |                                       |
            2 Fruit 11                               12 Meat 17
                |                                       |
    +------------------------+              +---------------------+
    |                        |               |                      |
3 Red 6               7 Yellow 10      13 Beef 14            15 Pork 16
    |                        |
4 Cherry 5               8 Banana 9

这样整个树状结构可以通过左右值来存储到数据库中。继续之前,我们看一看下面整理过的数据表。

parent     name    lft  rgt

                  Food       18    

 Food        |   Fruit    | 2   | 11  |
       |   Fruit  |    Red     | 3   |  6  |
       |   Red    |    Cherry  | 4   |  5  |
        |   Fruit  |    Yellow  | 7   | 10  |
       |   Yellow |    Banana  | 8   |  9  |
       |   Food   |    Meat    | 12  | 17  |
       |   Meat   |    Beef    | 13  | 14  |
       |   Meat   |    Pork    | 15  | 16  |

注意:由于"left""right" SQL中有特殊的意义,所以我们需要用"lft""rgt"来表示左右字段。 另外这种结构中不再需要"parent"字段来表示树状结构。也就是 说下面这样的表结构就足够了。

|    name    | lft | rgt |
     +------------+-----+-----+
     |    Food    | 1   | 18  |
      |   Fruit    | 2   | 11  |
      |    Red     | 3   |  6  |
      |    Cherry  | 4   |  5  |
     |    Yellow  | 7   | 10  |
     |    Banana  | 8   |  9  |
    |    Meat    | 12  | 17  |
    |    Beef    | 13  | 14  |
    |    Pork    | 15  | 16  |

好了我们现在可以从数据库中获取数据了,例如我们需要得到"Fruit"项下的所有所有节点就可以这样写查询语句: SELECT * FROM tree WHERE lft BETWEEN 2 AND 11; 这个查询得到了以下的结果。

|    name    | lft | rgt |
+------------+-----+-----+
|   Fruit    | 2   | 11  |
|    Red     | 3   |  6  |
|    Cherry  | 4   |  5  |
|    Yellow  | 7   | 10  |
|    Banana  | 8   |  9  |

在这种结构设计中可以很容易的算出某节点又多少个子节点,要查询某个父节点的子节点时也只需要查询这个父亲节点的左值和右值来确定。

1.2.2              预排序遍历树算法的优缺点分析

优点:进行查询时速度很快,因为它只需要进行简单数值比较。可以很方便的计算出每个父亲节点又多少子节点(其实这大部分的工作量都是在建表过程中完成的)。

缺点:如果在已经预排序好的表中要插入新的数据,势必会破坏原有表的结构,导致表中某些数据的左值和右值会改变,当然,如果插入到了根节点的位置,那么每个节点的左值和右值都会发生改变。

 

1.3         比较笨拙的一种层次结构设计

1.3.1 该结构的描述

大致思想是将第一层的编号设为01,02,03…,第二层设为0101,0102,0103…,0201,0202,0203,0204……。这种方式的主要好处在于一看数据库中的数据表结构就清晰明了。国内著名的erp公司用友公司绝大部分层次数据都采用的是这种设计。

1.3.2              实例分析

     在肿瘤数据库中,本人对于肿瘤病种的设计采用的就是这种结构。表的第一个字段为id字段,第二个字段为病种名称字段。

if (!Page.IsPostBack)

        {

            string str = "server=dbserver;database=tumour;uid=sa;pwd=sa";

            SqlConnection sqlconn = new SqlConnection(str);

            SqlDataAdapter sda = new SqlDataAdapter("select * from tnm", sqlconn);

            DataTable dt = new DataTable();

            sda.Fill(dt);

 

            //从树中显示

            TreeNode tn1 = new TreeNode("肿瘤病种");

            TreeView1.Nodes.Add(tn1);

            for (int i = 0; i < dt.Rows.Count; i++)

            {

                if (dt.Rows[i][0].ToString().Trim().Length == 2)

                {

                    TreeNode tdsheng = new TreeNode(dt.Rows[i][1].ToString().Trim());

                    tn1.ChildNodes.Add(tdsheng);

                    for (int j = 0; j < dt.Rows.Count; j++)

                    {

                        if (dt.Rows[j][0].ToString().Trim().Length == 4 && dt.Rows[j][0].ToString().Trim().Substring(0, 2) == dt.Rows[i][0].ToString().Trim())

                        {

                            TreeNode tdsheng1 = new TreeNode(dt.Rows[j][1].ToString().Trim());

                            tdsheng.ChildNodes.Add(tdsheng1);

 

                            for (int k = 0; k < dt.Rows.Count; k++)

                            {

                                if (dt.Rows[k][0].ToString().Trim().Length == 6 && dt.Rows[k][0].ToString().Trim().Substring(0, 4) == dt.Rows[j][0].ToString().Trim())

                                {

                                    TreeNode tdsheng2 = new TreeNode(dt.Rows[k][1].ToString().Trim());

                                    tdsheng1.ChildNodes.Add(tdsheng2);

                                    for (int l = 0; l < dt.Rows.Count; l++)

                                    {

                                        if (dt.Rows[l][0].ToString().Trim().Length == 8 && dt.Rows[l][0].ToString().Trim().Substring(0, 6) == dt.Rows[k][0].ToString().Trim())

                                        {

                                            TreeNode tdsheng3 = new TreeNode(dt.Rows[l][1].ToString().Trim());

                                            tdsheng2.ChildNodes.Add(tdsheng3);

                                           

                                        }

                                    }

 

                                }

                            }

                        }

                    }

                }

            }

显示第一层的时候取id字符串长度为2,显示第二层时取id字符串长度为4且该id的前两个字符串和他的父节点的id字符串相等,依次类推,得出整个树形结构。

1.3.3            该结构的分析

      优点:从表中通过id就可以很清楚的看清楚树状结构,对于从来没有设计过层次数据库的工程人员,这种方式比较容易想到。

      缺点:每一层如果节点数目太多超过了99个该结构将无法直接解决这种问题,在查询显示过程中采用的是嵌套循环,所以系统开销也比较大,查询每一层的时候都需要遍历所有节点。

 

1.4        分表保存每一层结构

该结构其实是针对上一节我们所讨论的结构的一种扩展,即把每一层都单独的放在一个表中,这样尽管表的数量会增加,但是对于每一层的具体节点的查找显示是很方便的,对于数据量很少的层次结构完全可以采用这种结构。

综上要采用哪种层次结构的设计方式,首先必须对该事物的业务逻辑有很清晰的了解,主要要从数据量的大小和数据的扩展性还有数据的用途上面考虑,选择一种最合适的设计方案,也就是说在层次数据库设计中没有最好的只有最合适的。

原创粉丝点击