首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 数据库 > Mysql >

MySql无限归类结构

2012-11-01 
MySql无限分类结构无限分类是我们开发中非常常见的应用,像论坛的的版块,CMS的类别,应用的地方特别多。我们

MySql无限分类结构

无限分类是我们开发中非常常见的应用,像论坛的的版块,CMS的类别,应用的地方特别多。
我们最常见最简单的方法就是在MySql里ID ,parentID,name:
优点是简单,结构简单。
缺点是效率不高,因为每一次递归都要查询数据库,几百条数据库时就不是很快了!

存储树是一种常见的问题,多种解决方案。主要有两种方法:邻接表的模型,并修改树前序遍历算法。

我们将探讨这两种方法的节能等级的数据。我会使用树从一个虚构的网上食品商店作为一个例子。这食品商店组织其食品类,通过颜色和类型。这棵树看起来像这样:
MySql无限归类结构
下面我们将用另外一种方法,这就是预排序遍历树算法(modified?preorder?tree?traversal?algorithm)?
这种方法大家可能接触的比较少,初次使用也不像上面的方法容易理解,但是由于这种方法不使用递归查询算法,有更高的查询效率。
我们首先将多级数据按照下面的方式画在纸上,在根节点Food的左侧写上?1?然后沿着这个树继续向下?在?Fruit?的左侧写上?2?然后继续前进,沿着整个树的边缘给每一个节点都标上左侧和右侧的数字。最后一个数字是标在Food?右侧的?18。?在下面的这张图中你可以看到整个标好了数字的多级结构。(没有看懂?用你的手指指着数字从1数到18就明白怎么回事了。还不明白,再数一遍,注意移动你的手指)。?
这些数字标明了各个节点之间的关系,"Red"的号是3和6,它是?"Food"?1-18?的子孙节点。?同样,我们可以看到?所有左值大于2和右值小于11的节点?都是"Fruit"?2-11?的子孙节点?

如图所示:
MySql无限归类结构

这样整个树状结构可以通过左右值来存储到数据库中。继续之前,我们看一看下面整理过的数据表。?
MySql无限归类结构
注意:由于"left"和"right"在?SQL中有特殊的意义,所以我们需要用"lft"和"rgt"来表示左右字段。?另外这种结构中不再需要"parent"字段来表示树状结构。也就是?说下面这样的表结构就足够了。?

SELECT * FROM tree WHERE lft BETWEEN 2 AND 11;

MySql无限归类结构
看到了吧,只要一个查询就可以得到所有这些节点。为了能够像上面的递归函数那样显示整个树状结构,我们还需要对这样的查询进行排序。用节点的左值进行排序:?

SELECT * FROM tree WHERE lft BETWEEN 2 AND 11 ORDER BY lft ASC;

那么某个节点到底有多少子孙节点呢?很简单,子孙总数=(右值-左值-1)/2?
descendants?=?(right?–?left?-?1)?/?2?,如果不是很清楚这个公式,那就去翻下书,我们在上数据结构写的很清楚!

添加同一层次的节点的方法如下:

<?php function display_tree($root) {    // retrieve the left and right value of the $root node    $result = mysql_query('SELECT lft, rgt FROM tree '.                           'WHERE title="'.$root.'";');    $row = mysql_fetch_array($result);    // start with an empty $right stack    $right = array();    // now, retrieve all descendants of the $root node    $result = mysql_query('SELECT title, lft, rgt FROM tree '.                           'WHERE lft BETWEEN '.$row['lft'].' AND '.                           $row['rgt'].' ORDER BY lft ASC;');    // display each row    while ($row = mysql_fetch_array($result)) {        // only check stack if there is one        if (count($right)>0) {            // check if we should remove a node from the stack            while ($right[count($right)-1]<$row['rgt']) {                array_pop($right);            }        }        // display indented node title        echo str_repeat('  ',count($right)).$row['title']."\n";        // add this node to the stack        $right[] = $row['rgt'];    } } ?>

8 楼 utyphoon 2008-10-16   是个不错的思路。 9 楼 utom75 2008-10-16   这是B-tree,如果一个node有超过两个以上的子node,这种解决方法不适用了 10 楼 zzm_fly2004 2008-10-16   采用id,parentId结构的树状数据结构,查找一个节点下所有子节点也可以不使用递归方法。可以在表结构中维护一个fullPath字段:根节点到当前节点的ID链。比如:
ROOT(1)
|
|--A(2)
   |--C(4)
   |  |--D(5)
   |
   |--B(3)
      |--E(6)
括弧里面表示节点ID,那么
id      parent       name        fullPath
1         0          ROOT         0-1
2         1           A           0-1-2
3         1           B           0-1-3
4         2           C           0-1-2-4
5         4           D           0-1-2-4-5
6         3           E           0-1-3-6
假设要找A节点C,D下所有的子节点,SQL如下:
select * from table t where t.fullPath like '%2%';

但效率怎样,没有测试过。 11 楼 moonranger 2008-10-16   对于分类这种通常变化不大的数据,用这种方式的确不错,提高了不少效率。但是如果分类变化比较频繁,那很难说效率能高多少,因为对分类进行改变时太麻烦,效率不高。 12 楼 waterdh 2008-10-16   我觉得比较好的方法是初始化的时候,全部装载到内存里。以后的更新删除等操作,同步内存和数据库。
数据结构:
class Tree{
  int treeId;
  String treeName;
  Tree parent;
  List<Tree> sons;
  .
  .
  other useful field;
}

根据需要使用HashMap作treeId ->Tree和treeName->Tree的映射索引。
这样增加和删除节点,更新下parent和sons,删除或更新数据库记录。
速度相当快。

13 楼 east_java 2008-10-16   呵呵..这个不错..

我一直用这种方式处理树形结构.

但如果加上Composite模式,就更好了.. 14 楼 xly_971223 2008-10-16   不错 但不太实用 插入 删除略显复杂 15 楼 SeanHe 2008-10-17   zzm_fly2004 写道
采用id,parentId结构的树状数据结构,查找一个节点下所有子节点也可以不使用递归方法。可以在表结构中维护一个fullPath字段:根节点到当前节点的ID链。比如: ROOT(1) | |--A(2) &nbsp;&nbsp; |--C(4) &nbsp;&nbsp; |&nbsp; |--D(5) &nbsp;&nbsp; | &nbsp;&nbsp; |--B(3) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |--E(6) 括弧里面表示节点ID,那么 id&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; parent&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fullPath 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ROOT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0-1 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0-1-2 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; B&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0-1-3 4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; C&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0-1-2-4 5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; D&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0-1-2-4-5 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; E&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0-1-3-6 假设要找A节点C,D下所有的子节点,SQL如下: select * from table t where t.fullPath like '%2%'; 但效率怎样,没有测试过。

你的fullpath应该这么写0-1-2-  ,查这一级的子节点应该用like '0-1-2-%',不然会吧0-1-21也当成0-1-2的下级了 16 楼 lijie250 2008-10-17   utom75 写道
这是B-tree,如果一个node有超过两个以上的子node,这种解决方法不适用了

可以多个子节点的,你按照上面的方法试就知道了! 17 楼 danielking 2008-10-17   貌似和rails里的betternestedset插件类似 18 楼 beyondqinghua 2008-10-17   这种方式太麻烦了,还不如用二进制相与来处理
0000 0000 0000 0000 0000 0000 。。。
一般来说一个64位的就足够你分了
第一级有2的四次方,如果觉得不够可以再多分几位,如果要查找第二级为哪个的子菜单只要与0000 1111 相与就是了,而且数据库本身就支持这种运算,所以非常方便,效率就更不用说了。唯一的麻烦就是要写个算法删除菜单后如何重新利用的问题!
19 楼 beyondqinghua 2008-10-17   beyondqinghua 写道
这种方式太麻烦了,还不如用二进制相与来处理 0000 0000 0000 0000 0000 0000 。。。 一般来说一个64位的就足够你分了 第一级有2的四次方,如果觉得不够可以再多分几位,如果要查找第二级为哪个的子菜单只要与0000 1111 相与就是了,而且数据库本身就支持这种运算,所以非常方便,效率就更不用说了。唯一的麻烦就是要写个算法删除菜单后如何重新利用的问题!

如果了解子网的算法那么这就很容易理解了。 20 楼 liudaoru 2008-10-18   呵呵,翻译的不错,不过最好把别人的文章链接放在前面,尊重别人的劳动成果:) 21 楼 lqixv 2008-12-10   查询还行,但每添加或删除一条数据的时候,就必须把所有的数据全部更新一遍,代价也很大。 22 楼 lijie250 2008-12-14   lqixv 写道
查询还行,但每添加或删除一条数据的时候,就必须把所有的数据全部更新一遍,代价也很大。

这个就要看需求了,我准备是用在CMS上,一般更改不大,主要想在查询方面效率高! 23 楼 thxg 2010-03-04   算法有点意思,不过处理确实有些复杂,可能是第一次看到这样处理树目录有些陌生。
我一般用parentId加逐级累加的字符串来处理,跨级查询子目录时用 like 'xxx-xxx-*'。like效率差些,但想着百分号在后面,或许还将就吧。

热点排行