mysql 存储层级关系_[数据库]数据库存储层级结构数据

本文介绍了如何在关系型数据库中存储层级结构数据,主要探讨了邻接表模型和修改后的树前序遍历算法。通过示例展示了在MySQL中存储食品商店的分类树,并使用Java实现显示树结构和获取节点路径的函数。邻接表模型简单直观但效率较低,而树前序遍历算法通过左右值减少查询次数,提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

无论你想建立自己的论坛,在你的网站上从一个邮件列表发布消息,或编写自己的cms:你将会遇到一种情况:将分层数据(层级结构)存储在数据库中。除非你使用一个类似xml的数据库,通用的关系型数据库是很难做到这一点。关系型数据库中的表不分层;他们只是一个简单

无论你想建立自己的论坛,在你的网站上从一个邮件列表发布消息,或编写自己的cms:你将会遇到一种情况:将分层数据(层级结构)存储在数据库中。除非你使用一个类似xml的数据库,通用的关系型数据库是很难做到这一点。关系型数据库中的表不分层;他们只是一个简单列表。你必须找到一个方法来把层次结构数据转换为一个简单文件数据。

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

在本文中,我们将探讨这两种方法来存储层级结构数据。我将使用从一个虚构的在线食品商店虚拟的这棵树为例。这个食品商店通过类别、颜色以及种类来来组织它的食品。

这颗树如下:

442fa5864ef0d2fcf2e7c6eae7d87862.gif

这篇文章包含一些代码示例展示了如何保存和检索数据。原文是使用的PHP,在这我将使用java来演示它的代码展示。

[一] 邻接列表模型

我们最先尝试或者最优雅的方式我们称为“邻接表模式”或者我们称它为“递归方法”。

这是一种优雅的方法,因为你只需要一个简单的函数就可以遍历树。在我们的食品商店的例子中,邻接表的表可以这样表示:

bb24c7a890b7872d8f122c03b7e7c1c5.gif

如您所见,在邻接表的方法,你只保存每个节点的父节点。我们可以从表上清楚的看到,“Pear”是“Green”的一个孩子,同时“Green”又是'Fruit'的孩子。

根节点“Food”没有父节点。为简单起见,我使用了“标题”值来确定每个节点。当然,在一个真正的数据库,可以使用id来标示。

eb93ae14b88d0198b3bddffaaefe4c1c.gif

Give Me the Tree

既然我们已经在数据库中插入我们的树,是时候写一个显示功能。这个函数将不得不从根节点(没有父节点)开始,应该显示所有该节点的孩子。对于这些孩子节点来说,这个函数应该检索和显示这些孩子节点的所有子节点。然后在显示这些孩子节点的子节点,递归的进行。

public void DisplayTree(int parentId,int level){

String sql = "select NodeId,NodeName,ParentId from Tree where parentid="+ parentId;

conn = sqlManager.GetConn();

try {

statement = conn.createStatement();

ResultSet rs = statement.executeQuery(sql);

while(rs.next()){

for(int i = 0;i < level*2;i++){

System.out.print("-");

}

System.out.println(rs.getString("NodeName"));

DisplayTree(rs.getInt("NodeId"),level+1);

}

} catch (SQLException e) {

e.printStackTrace();

}

}

注:sqlManager类是数据库操作类。

数据库存储的数据:

a1bfd306b604c4c9df175e09cf53f2ee.png

打印整个树

DisplayTree(0,0)

结果:

03bfe573d9da1596f1e021eef01633ca.png

如果你只是想看到一个子树,你可以告诉函数这个开始节点的Id即可。

例如,显示“Fruit”子树,运行DisplayTree(2,0);

The Path to a Node

有时候我们需要知道某个节点所在的路径。举例来说,“Cherry”所在的路径为Food > Fruit > Red > Cherry。在这里,我们可以从Cherry开始查起,然后递归查询查询节点前的节点,直到某节点的父节点ID为0。

//获取某个节点所在路径

public ArrayListGetPath(int nodeId){

String sql = "select ParentId,NodeName from Tree where NodeId = "+nodeId;

ArrayListpath = new ArrayList();

try {

conn = sqlManager.GetConn();

statement = conn.createStatement();

ResultSet rs = statement.executeQuery(sql);

rs.next();

int parentId = rs.getInt("ParentId");

String nodeName = rs.getString("NodeName");

if(parentId != 0){

path.addAll(GetPath(parentId));

}

path.add(nodeName);

} catch (SQLException e) {

e.printStackTrace();

}

return path;

}

ArrayListpath = main.GetPath(9);

int index = 0;

for (String node : path) {

if(index != 0){

System.out.print("->");

}

System.out.print(node);

index++;

}

id= 9 对应为Banana 路径为:Food->Fruit->Yellow->Banana

优缺点

我们可以看到,用邻接表模型确实是个不错的方法。它简单易懂,而且实现的代码写起来也很容易。那么,缺点是什么呢?那就是,在大多数语言中,邻接表模型执行起来效率低下和缓慢。这主要是由于递归引起的,我们查询树中的每个节点的时候都需要进行依次数据库查询。因为每个查询需要一些时间,这使得函数非常缓慢的在处理大树时。

第二个原因是在你可能使用的编程语言这个方法不是那么快,。对于一门程序语言来说,除了Lisp这种,大多数不是为了递归而设计。当一个节点深度为4时,它得同时生成4个函数实例,它们都需要花费时间、占用一定的内存空间。所以,邻接表模型效率的低下可想而知。

[二] 树前序遍历的算法变形

那么就让我们来看另外一种存储树形结构的方法。如之前所讲,我们希望能够减少查询的数量,最好是只做到查询一次数据库。

现在我们把树“横”着放。如下图所示,我们首先从根节点(“Food”)开始,先在它左侧标记“1”,然后我们到“Fruit”,左侧标记“2”,接着按照前序遍历的顺序遍历完树,依次在每个节点的左右侧标记数字。最后一个数字写在“Food”节点右边的。在这张图片里,你可以看到用数字标记的整个树和几个箭头指示编号顺序。

75c0093146f3b627cc4598f096675d85.gif

我们叫这些数字为Left和Right(例如“Food”的Left值是1,Right值是18)。正如你所看到的,这些数字表示每个节点之间的关系。

比如,“Red”节点左边的数为3、右边的数为6,它是Food(1-18)的后代。同样的,我们可以注意到,左数大于2、右数小于11的节点都是“Fruit”的子孙。现在,所有的节点将以左数-右数的方式存储,这种通过遍历一个树、然后给每一个节点标注左数、右数的方式称为修改过的前序遍历算法。

在我们继续之前,让我们来看看在我们的表的这些值:

080711c868920e59558c710f5b8eb2ef.gif

注意,“Left”和“Right”在SQL中有特殊的意义。因此,我们必须使用“lft”和“rgt”标识列。还要注意,我们并不真的需要“parent”列了。我们现在有lft和rgt值存储树结构。

Retrieve the Tree

如果你想使用一个带有左值和右值的表显示树与,你首先要确定你想要检索的节点。例如,如果您希望检索“Fruit”子树,你将不得不选择左值在2至11之间的那些节点。 sql语句:

SELECT * FROM FoodTree WHERE Lft BETWEEN 2 AND 11;

注:FoodTree是存储数据的表

返回:

ce116ad676632bb9c4f3c4e7496cb3d1.gif

整个树只需要一次查询。

显示这棵树就像我们做递归函数,我们将不得不ORDER BY子句添加到查询语句中。如果你从你的表添加和删除行,你的表可能不会以正确的顺序存储。我们应该按左值进行排序。

SELECT * FROM FoodTree WHERE Lft BETWEEN 2 AND 11 ORDER BY Lft ASC;

现在唯一的问题是缩进问题。

显示树结构,孩子应该缩进略高于他们的父母。

正如我们面对树的问题常常会想到的方案——栈。这里,我们可以维护一个只保存右数的栈。我们知道该节点所有的孩子的Rgt值小于父的Rgt值,所以通过比较当前节点的Rgt值和堆栈中最后一个节点的Rgt值。当当前节点的Rgt值大于栈顶元素的值(说明栈顶元素的子树都以遍历完毕),这个时候弹出栈顶值。再循环检查栈顶值,直到栈顶值小于当前查询节点的Rgt值。这个时候只要检查栈中元素,有多少个元素说明当前查询节点有多少个祖先节点(设为n)。只需要打印n个空格即可。代码如下:

f68f2add0b68e4f9810432fce46917b7.png

本文原创发布php中文网,转载请注明出处,感谢您的尊重!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值