• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

MySql无限归类结构

mysql 搞代码 7年前 (2018-06-04) 122次浏览 已收录 0个评论

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 ,如果不是很清楚这个公式,那就去翻下书,我们在上数据结构写的很清楚!

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

LOCK TABLE nested_category WRITE;   SELECT @myRight := rgt FROM nested_category WHERE name = 'Cherry';    UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight; UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight;  INSERT INTO nested_category(name, lft, rgt) VALUES('Strawberry', @myRight + 1, @myRight + 2);  UNLOCK TABLES;

欢迎大家阅读《MySql无限归类结构》,跪求各位点评,by 搞代码

 

 

 

添加树的子节点的方法如下:

LOCK TABLE nested_category WRITE;  SELECT @myLeft := lft FROM nested_category  WHERE name = 'Beef';  UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myLeft; UPDATE nested_category SET lft = lft + 2 WHERE lft > @myLeft;  INSERT INTO nested_category(name, lft, rgt) VALUES('charqui', @myLeft + 1, @myLeft + 2);  UNLOCK TABLES;

 

 

 

每次插入节点之后都可以用以下SQL进行查看验证:

SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name FROM nested_category AS node, nested_category AS parent WHERE node.lft BETWEEN parent.lft AND parent.rgt GROUP BY node.name ORDER BY node.lft; 

 

 

 

删除节点的方法,稍微有点麻烦是有个中间变量,如下:

LOCK TABLE nested_category WRITE;   SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 FROM nested_category WHERE name = 'Cherry';   DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;   UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight; UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;  UNLOCK TABLES;

 ;

 

 

 

这种方式就是有点难的理解,但是适合数据量很大规模使用,查看所有的结构只需要两条SQL语句就可以了,在添加节点和删除节点的时候略显麻烦,不过相对于效率来说还是值得的,这次发现让我发现了数据库结构真的很有用,但是我在学校学的树基本上都忘记了,这次遇到这个问题才应用到项目中!

 

应用到我的小站上http://news.wangmeng.cn/

参考文章:
http://dev.mysql.com/tech-resources/articles/hierarchical-data.html

http://www.sitepoint.com/article/hierarchical-data-database/3/

4 楼 frank.zhang 2008-10-15  
不是很清楚楼主指的查询效率高是指的哪里。
假设系统中有一个类是Tree,用来表示树结构,有rootNode、childNodes属性等。
系统中另外一个类为TreeNode,表示树节点,有childNodes属性等。
如果我要构建出一个Tree对象来,楼主的办法的查询次数会比使用ID ,parentID,name的次数少吗?
如果一次把所有的数据查询出来,使用内存构建Tree对象,那么两种存储结构的查询次数也都是一次吧?
5 楼 lijie250 2008-10-15  
魔力猫咪 写道
好像是很不错的解决方法。不知道用JPA应该如何写呢?

我正在用STRUTS2+spirng2+ibatis2做个例子出来,其实很简单,理解原理,写一些操就可以了!

6 楼 supercode 2008-10-15  
不错的思路
一般WEB模式下,很少一次把所有节点递归出来给客户的
7 楼 lijie250 2008-10-15  
frank.zhang 写道
不是很清楚楼主指的查询效率高是指的哪里。 假设系统中有一个类是Tree,用来表示树结构,有rootNode、childNodes属性等。 系统中另外一个类为TreeNode,表示树节点,有childNodes属性等。 如果我要构建出一个Tree对象来,楼主的办法的查询次数会比使用ID ,parentID,name的次数少吗? 如果一次把所有的数据查询出来,使用内存构建Tree对象,那么两种存储结构的查询次数也都是一次吧?

你可能还没听懂我的意思,这个思路的意思就是为了减少数据库的压力,只要一条SQL语言就可以查出来,剩下的工作用程序来做逻辑判断,来显示树形的层次形式!这是php代码:

<?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效率差些,但想着百分号在后面,或许还将就吧。

搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:MySql无限归类结构

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址