`

在数据库中存储层次数据实现无限级分层

阅读更多
在数据库中存储层次数据实现无限级分层

无论你要构建自己的论坛,在你的网站上发布消息还是书写自己的cms程序,你都会遇到要在数据库中存储层次数据的情况。同时,除非你使用一种像XML的数据库,否则关系数据库中的表都不是层次结构的,他们只是一个平坦的列表。所以你必须找到一种把层次数据库转化的方法。

存储树形结构是一个很常见的问题,他有好几种解决方案。主要有两种方法:邻接列表模型和改进前序遍历树算法。

在本文中,我们将探讨这两种保存层次数据的方法。我将举一个在线食品店树形图的例子。这个食品店通过类别、颜色和品种来组织食品。树形图如下:
树结构图

邻接列表模型:就是我们一般的设计方法,使用递归查询数据,具体见:http://www.52web.com/52article/?view-250-page-1.html

下面介绍改进前序遍历树

改进前序遍历树

现在,让我们看另一种存储树的方法。递归可能会很慢,所以我们就尽量不使用递归函数。我们也想尽量减少数据库查询的次数。最好是每次只需要查询一次。

我们先把树按照水平方式摆开。从根节点开始(“Food”),然后他的左边写上1。然后按照树的顺序(从上到下)给“Fruit”的左边写上2。这样,你沿着树的边界走啊走(这就是“遍历”),然后同时在每个节点的左边和右边写上数字。最后,我们回到了根节点“Food”在右边写上18。下面是标上了数字的树,同时把遍历的顺序用箭头标出来了。
层次标记图

我们称这些数字为左值和右值(如,“Food”的左值是1,右值是18)。正如你所见,这些数字按时了每个节点之间的关系。因为“Red”有3和6两个值,所以,它是有拥有1-18值的“Food”节点的后续。同样的,我们可以推断所有左值大于2并且右值小于11的节点,都是有2-11的“Food”节点的后续。这样,树的结构就通过左值和右值储存下来了。这种数遍整棵树算节点的方法叫做“改进前序遍历树”算法。

在继续前,我们先看看我们的表格里的这些值:
数据读取1

注意单词“left”和“right”在SQL中有特殊的含义。因此,我们只能用“lft”和“rgt”来表示这两个列。(译注——其实Mysql中可以用“`”来表示,如“`left`”,MSSQL中可以用“[]”括出,如“[left]”,这样就不会和关键词冲突了。)同样注意这里我们已经不需要“parent”列了。我们只需要使用lft和rgt就可以存储树的结构。

获取树

如果你要通过左值和右值来显示这个树的话,你要首先标识出你要获取的那些节点。例如,如果你想获得“Fruit”子树,你要选择那些左值在2到11的节点。用SQL语句表达:

  1. SELECT*FROMtreeWHERElftBETWEEN2AND11;

这个会返回:
数据读取2

好吧,现在整个树都在一个查询中了。现在就要像前面的递归函数那样显示这个树,我们要加入一个ORDER BY子句在这个查询中。如果你从表中添加和删除行,你的表可能就顺序不对了,我们因此需要按照他们的左值来进行排序。

  1. SELECT*FROMtreeWHERElftBETWEEN2AND11ORDERBYlftASC;

就只剩下缩进的问题了。

要显示树状结构,子节点应该比他们的父节点稍微缩进一些。我们可以通过保存一个右值的一个栈。每次你从一个节点的子节点开始时,你把这个节点的右值添加到栈中。你也知道子节点的右值都比父节点的右值小,这样通过比较当前节点和栈中的前一个节点的右值,你可以判断你是不是在显示这个父节点的子节点。当你显示完这个节点,你就要把他的右值从栈中删除。要获得当前节点的层数,只要数一下栈中的元素。

  1. <?php
  2. functiondisplay_tree($root){
  3. //获得$root节点的左边和右边的值
  4. $result=mysql_query('SELECTlft,rgtFROMtree'.
  5. 'WHEREtitle="'.$root.'";');
  6. $row=mysql_fetch_array($result);
  7. //以一个空的$right栈开始
  8. $right=array();
  9. //现在,获得$root节点的所有后序
  10. $result=mysql_query('SELECTtitle,lft,rgtFROMtree'.
  11. 'WHERElftBETWEEN'.$row['lft'].'AND'.
  12. $row['rgt'].'ORDERBYlftASC;');
  13. //显示每一行
  14. while($row=mysql_fetch_array($result)){
  15. //检查栈里面有没有元素
  16. if(count($right)>0){
  17. //检查我们是否需要从栈中删除一个节点
  18. while($right[count($right)-1]<$row['rgt']){
  19. array_pop($right);
  20. }
  21. }
  22. //显示缩进的节点标题
  23. echostr_repeat('',count($right)).$row['title']."\n";
  24. //把这个节点添加到栈中
  25. $right[]=$row['rgt'];
  26. }
  27. }
  28. ?>

如果运行这段代码,你可以获得和上一部分讨论的递归函数一样的结果。而这个函数可能会更快一点:他不采用递归而且只是用了两个查询。

节点的路径

有了新的算法,我们还要另找一种新的方法来获得指定节点的路径。这样,我们就需要这个节点的祖先的一个列表。

由于新的表结构,这不需要花太多功夫。你可以看一下,例如,4-5的“Cherry”节点,你会发现祖先的左值都小于4,同时右值都大于5。这样,我们就可以使用下面这个查询:

  1. SELECTtitleFROMtreeWHERElft<4ANDrgt>5ORDERBYlftASC;

注意,就像前面的查询一样,我们必须使用一个ORDER BY子句来对节点排序。这个查询将返回:

+-------+
| title |
+-------+
| Food |
| Fruit |
| Red |
+-------+

我们现在只要把各行连起来,就可以得到“Cherry”的路径了。

有多少个后续节点?How Many Descendants

如果你给我一个节点的左值和右值,我就可以告诉你他有多少个后续节点,只要利用一点点数学知识。因为每个后续节点依次会对这个节点的右值增加2,所以后续节点的数量可以这样计算:

descendants = (right – left - 1) / 2

利用这个简单的公式,我可以立刻告诉你2-11的“Fruit”节点有4个后续节点,8-9的“Banana”节点只是1个子节点,而不是父节点。

自动化树遍历

现在你对这个表做一些事情,我们应该学习如何自动的建立表了。这是一个不错的练习,首先用一个小的树,我们也需要一个脚本来帮我们完成对节点的计数。

让我们先写一个脚本用来把一个邻接列表转换成前序遍历树表格。

  1. <?php
  2. functionrebuild_tree($parent,$left){
  3. //这个节点的右值是左值加1
  4. $right=$left+1;
  5. //获得这个节点的所有子节点
  6. $result=mysql_query('SELECTtitleFROMtree'.
  7. 'WHEREparent="'.$parent.'";');
  8. while($row=mysql_fetch_array($result)){
  9. //对当前节点的每个子节点递归执行这个函数
  10. //$right是当前的右值,它会被rebuild_tree函数增加
  11. $right=rebuild_tree($row['title'],$right);
  12. }
  13. //我们得到了左值,同时现在我们已经处理这个节点我们知道右值的子节点
  14. mysql_query('UPDATEtreeSETlft='.$left.',rgt='.
  15. $right.'WHEREtitle="'.$parent.'";');
  16. //返回该节点的右值+1
  17. return$right+1;
  18. }
  19. ?>

这是一个递归函数。你要从rebuild_tree('Food',1); 开始,这个函数就会获取所有的“Food”节点的子节点。

如果没有子节点,他就直接设置它的左值和右值。左值已经给出了,1,右值则是左值加1。如果有子节点,函数重复并且返回最后一个右值。这个右值用来作为“Food”的右值。

递归让这个函数有点复杂难于理解。然而,这个函数确实得到了同样的结果。他沿着树走,添加每一个他看见的节点。你运行了这个函数之后,你会发现左值和右值和预期的是一样的(一个快速检验的方法:根节点的右值应该是节点数量的两倍)。

添加一个节点

我们如何给这棵树添加一个节点?有两种方式:在表中保留“parent”列并且重新运行rebuild_tree() 函数——一个很简单但却不是很优雅的函数;或者你可以更新所有新节点右边的节点的左值和右值。

第一个想法比较简单。你使用邻接列表方法来更新,同时使用改进前序遍历树来查询。如果你想添加一个新的节点,你只需要把节点插入表格,并且设置好parent列。然后,你只需要重新运行rebuild_tree() 函数。这做起来很简单,但是对大的树效率不高。

第二种添加和删除节点的方法是更新新节点右边的所有节点。让我们看一下例子。我们要添加一种新的水果——“Strawberry”,作为“Red”的最后一个子节点。首先,我们要腾出一个空间。“Red”的右值要从6变成8,7-10的“Yellow”节点要变成9-12,如此类推。更新“Red”节点意味着我们要把所有左值和右值大于5的节点加上2。

我们用一下查询:

  1. UPDATEtreeSETrgt=rgt+2WHERErgt>5;
  2. UPDATEtreeSETlft=lft+2WHERElft>5;

现在我们可以添加一个新的节点“Strawberry”来填补这个新的空间。这个节点左值为6右值为7。

  1. INSERTINTOtreeSETlft=6,rgt=7,title='Strawberry';

如果我们运行display_tree() 函数,我们将发现我们新的“Strawberry”节点已经成功地插入了树中:

Food
Fruit
Red
Cherry
Strawberry
Yellow
Banana
Meat
Beef
Pork

缺点

首先,改进前序遍历树算法看上去很难理解,它当然没有邻接列表方法简单。然而,一旦你习惯了左值和右值这两个属性,他就会变得清晰起来,你可以用这个技术来完成临街列表能完成的所有事情,同时改进前序遍历树算法更快。当然,更新树需要很多查询,要慢一点,但是取得节点却可以只用一个查询。

总结

你现在已经对两种在数据库存储树方式熟悉了吧。虽然在我这儿改进前序遍历树算法性能更好,但是也许在你特殊的情况下邻接列表方法可能表现更好一些。这个就留给你自己决定了。

最后一点:就像我已经说过我不推荐你使用节点的标题来引用这个节点。你应该遵循数据库标准化的基本规则。我没有使用数字标识是因为用了之后例子就比较难读。

分享到:
评论

相关推荐

    [其他类别]简单三层实现的无限级DropDownList_dropdownlist.zip

    总结,该资源提供了一个使用三层架构实现无限级DropDownList的实例,开发者可以通过学习此示例,掌握如何在ASP.NET中构建动态、分层的用户界面,以及如何处理层次结构数据的展示。这对于提升Web开发技能和理解分层...

    ASP.NET源码——简单三层实现的无限级DropDownList.zip

    在实现无限级DropDownList时,BLL可能会包含递归方法,用于生成多级数据结构,并提供给表现层使用。这些方法可能包括获取父级类别及其子类别,以及处理选择变化时的逻辑。 3. 数据访问层(Data Access Layer, DAL)...

    漂亮无限级分类源代码(三层实现).zip

    在IT行业中,无限级分类是一种常见的数据组织方式,特别是在数据库设计、网站导航、文件系统等领域。这个"漂亮无限级分类源代码(三层实现).zip"文件提供了一种优雅的解决方案,用于处理具有无限层级的分类结构。...

    树形目录的无限级扩展

    - **inode(索引节点)**:在某些文件系统中,如Linux的EXT系列,每个文件或目录都有一个唯一的inode,存储了文件的元数据,如权限、所有权、修改时间等,以及指向数据块的指针。 - **递归**:在编程中,处理无限级...

    asp.net 无限级分类

    在非标准的三层架构中,可能不再严格遵循这些分层,比如可能会将数据访问逻辑直接融入业务逻辑层,或者使用自定义的存储过程和数据访问组件。 对于无限级分类,我们需要考虑以下关键点: 1. 数据库设计:通常,...

    Ylasa简单treeview无限级分类源码

    5. **Access数据库**:Microsoft Access数据库被用作后端数据存储,用于保存树形视图中的分类信息。Access数据库适合小型项目,易于管理和维护。 6. **ASP.NET源码**:`Manage_Type_Class.aspx.cs`和`Default.aspx....

    无限级分类 三层模式开发 Asp.net

    在IT行业中,无限级分类是一种常见的数据组织方式,特别是在网站内容管理、商品分类、组织架构等领域。Asp.net 是微软公司推出的一种强大的Web应用程序开发框架,它提供了丰富的工具和库来帮助开发者构建高效、安全...

    jsp源码其他类别JSP无限级分类目录树-sorttree

    1. **数据准备**:首先需要准备一份符合要求的数据集,这些数据通常存储在数据库中,通过SQL查询获取。 2. **递归算法**:使用递归来处理数据,构建出层次分明的数据结构。 3. **前端渲染**:利用JSP页面来渲染数据...

    无限级树形(三层开发)源码

    在“无限级树形(三层开发)”的场景中,我们将探讨如何在.NET环境中实现一个能够处理无限级分类或层级关系的数据结构,并通过三层架构来组织代码。 首先,表现层(UI)是用户与系统交互的部分。在无限级树形结构中,...

    无限级分类源代码_dotnet整站程序.rar

    在.NET开发环境中,无限级分类是一种常见的数据组织方式,它被广泛应用于网站的导航菜单、产品分类、组织结构等场景。这个"无限级分类源代码_dotnet整站程序.rar"文件提供了一个完整的解决方案,用于创建和支持网站...

    SystemUserDAOImpl.rar_SystemUserDaoImpl_xml classification

    在这个场景中,我们关注的是`SystemUserDAOImpl`,一个实现系统用户数据访问对象(DAO)的类,它涉及到对用户数据的无限级分类以及XML格式的数据转换。下面我们将详细探讨这些关键知识点。 首先,`...

    ClosureTable:Laravel框架的邻接表的Closure Table数据库设计模式实现

    当您需要在数据库中存储和操作分层数据时,可能需要使用它。 该软件包是一种称为的著名设计模式的实现。 但是,为了简化和优化SQL SELECT查询,它使用邻接表来查询直接的父/子关系。 内容: 示例→ 示例→ 示例→ ...

    存储过程递归查询

    在数据库管理中,递归查询主要用于处理层级结构数据,如组织架构、产品分类等。递归查询可以方便地遍历多级关系,实现对无限层级的数据进行查询。 #### 二、SQL递归查询语法介绍 递归查询主要通过`WITH`子句实现,...

    PHP两种实现无级递归分类的方法

    在实现无限级分类时,递归不仅可以实现代码的简化,而且可以使得整个分类逻辑更加直观易懂。 在实际应用中,递归分类还可以结合前端技术,例如使用JavaScript来动态显示树状菜单,或者在PHP后端处理数据后通过AJAX...

    php无限极分类实现的两种解决方法

    在PHP中实现无限极分类是网站开发中常见的需求,主要被用于处理具有层级结构的数据,如商品分类、文章分类等。无限极分类通常意味着可以无限制地对数据进行分层,每一级分类都可以拥有下一级的子分类。本文将介绍两...

Global site tag (gtag.js) - Google Analytics