首页 > 数据库 >PHP和MySQL处理树状、分级、无限分类、分层数据的方法

PHP和MySQL处理树状、分级、无限分类、分层数据的方法

时间:2023-02-07 14:34:51浏览次数:68  
标签:category rgt 树状 -- id lft MySQL PHP 节点


文章标题中的多个词语表达的其实是一个意思,就是递归分类数据,分级数据非常类似数据结构中的树状结构,即每个节点有自己的孩子节点,孩子结点本身也是父亲节点。这是一个递归、分层形式。可以称之为树形层级数据。

层级数据结构是编程语言中非常普通的一种数据结构,它代表一系列的数据每一项都有一个父亲节点(除了根节点)和其他多个孩子结点。WEB开发人员使用层级数据结构用于非常多的场景,包括内容管理系统CMS、论坛主题、邮件列表,还有电子商务网站的产品分类等。

本文章主要介绍了使用PHP和MYSQL来管理分级数据的方法,在其中将给出两种最流行的分级数据模型:

  • 邻接表模型
  • 嵌套集合模型




邻接表模型用于分层数据

邻接表模型是一种分级数据模型,其中每个节点有一个指向其父亲的指针(根节点该指针为空值),使用下面的SQL语句将建立该结构并插入测试数据:

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

--
-- 表的结构 ` category `
--

CREATE TABLE IF NOT EXISTS ` category ` (
` category_id ` int ( 10 ) NOT NULL AUTO_INCREMENT ,
` category_name ` varchar ( 50 ) NOT NULL ,
` parent_id ` int ( 10 ) DEFAULT NULL ,
PRIMARY KEY ( ` category_id ` )
) ENGINE = InnoDB DEFAULT CHARSET = utf8 AUTO_INCREMENT = 15 ;

--
-- 转存表中的数据 ` category `
--

INSERT INTO ` category ` ( ` category_id ` , ` category_name ` , ` parent_id ` ) VALUES
( 1 , 'A' , NULL ) ,
( 2 , 'B' , 1 ) ,
( 3 , 'C' , 1 ) ,
( 4 , 'D' , 1 ) ,
( 5 , 'E' , 2 ) ,
( 6 , 'F' , 2 ) ,
( 7 , 'I' , 4 ) ,
( 8 , 'G' , 5 ) ,
( 9 , 'H' , 5 ) ,
( 10 , 'J' , 7 ) ,
( 11 , 'K' , 10 ) ,
( 12 , 'L' , 10 ) ;


建立完成后,数据库中存在了数据,并且分类图是每个节点为(关键字:数据库ID)。


parent_id就是它的父节点的ID号,这种方法非常简单,因为能很容易的看清楚父子关系。使用以下的简单PHP函数代码可以很容易的输出树状路径:

<?php

function get_path ( $category_id )
{
$con = mysql_connect ( "localhost" , "root" , "123456" ) ;
if ( ! $con ) {
die ( '数据库连接失败: ' . mysql_error ( ) ) ;
}

mysql_select_db ( 'test' , $con ) ;

// 查找当前节点的父节点的ID,这里使用表自身与自身连接实现
$sql = "
SELECT c1.parent_id, c2.category_name AS parent_name
FROM category AS c1

LEFT JOIN category AS c2
ON c1.parent_id=c2.category_id

WHERE c1.category_id='$category_id' " ;
//echo $sql."<br>";//测试把SQL打印出来,拿到数据库执行一下看看结果
$result = mysql_query ( $sql ) ;
$row = mysql_fetch_array ( $result ) ; //现在$row数组存了父亲节点的ID和名称信息

// 将树状路径保存在数组里面
$path = array ( ) ;

//如果父亲节点不为空(根节点),就把父节点加到路径里面
if ( $row [ 'parent_id' ] != NULL )
{
//将父节点信息存入一个数组元素
$parent [ 0 ] [ 'category_id' ] = $row [ 'parent_id' ] ;
$parent [ 0 ] [ 'category_name' ] = $row [ 'parent_name' ] ;

//递归的将父节点加到路径中
$path = array_merge ( get_path ( $row [ 'parent_id' ] ) , $parent ) ;
}

return $path ;
}

//根据上面的图可以看出,K的ID是11,我们就用它来测试路径
$path = get_path ( 11 ) ;
echo "<h2>路径数组:</h2>" ;
echo "<pre>" ;
print_r ( $path ) ;
echo "</pre>" ;
//将路径到根节点的路径打印出来
//打印结果:J>I>D>A>
echo "<h2>向根节点打印路径:</h2>" ;

for ( $i = count ( $path ) - 1 ; $i >= 0 ; $i -- )
{
echo $path [ $i ] [ 'category_name' ] . '>' ;
}

?>

由此可以知道怎样找到一个叶子节点(没有孩子的节点)到根节点的路径,下面来看怎样从根节点往下来遍历层级结构,通过节点的层级关系来打印所有的节点:

<?php
function display_children ( $category_id , $level )
{
$con = mysql_connect ( "localhost" , "root" , "123456" ) ;
if ( ! $con ) {
die ( '数据库连接失败: ' . mysql_error ( ) ) ;
}

mysql_select_db ( 'test' , $con ) ;
// 获得当前节点的所有孩子节点(直接孩子,没有孙子)
$result = mysql_query ( "SELECT * FROM category WHERE parent_id='$category_id'" ) ;

// 遍历孩子节点,打印节点
while ( $row = mysql_fetch_array ( $result ) )
{
// 根据层级,按照缩进格式打印节点的名字
// 这里只是打印,你可以将以下代码改成其他,比如把节点信息存储起来
echo str_repeat ( '--' , $level ) . $row [ 'category_name' ] . "<br/>" ;

// 递归的打印所有的孩子节点
display_children ( $row [ 'category_id' ] , $level + 1 ) ;
}
}

//根节点是A:1我们就用它来打印所有的节点
display_children ( 1 , 0 ) ;
?>

 

然而,邻接表模型(每个节点存储父亲节点信息)有它的劣势,首先使用数据库的查询语句很难直接实现它,需要借助PHP代码实现。SQL语句需要你知道节点位于哪一个层级。并且每个树层是使用SQL的自我表连接实现的,这意味着树的每一层处理都会降低数据库的性能。

删除节点的过程也会导致一些问题,如果只删除了某个节点它却有孩子,结果是它的孩子成了孤儿(就是没有父亲了),真正的体现就是,这些孩子节点从树中相当于“消失了”。

嵌套集合模型用于树形分层结构数据

嵌套集合模型,也叫做先根遍历树算法,也是一种处理树形层级数据的方法。代替节点间的父子关系,层级使用嵌套的容器的集合来表示,其中每个节点具有两个值,一个left,一个right。

决定left和right的值的过程是从左到右进行的,首先给left赋值,让后向下遍历节点的孩子们,最后才能得到节点的right的值。SQL语句如下所示:

CREATE TABLE category (
category_id INT ( 10 ) AUTO_INCREMENT PRIMARY KEY ,
category_name VARCHAR ( 50 ) NOT NULL ,
lft INT ( 10 ) NOT NULL ,
rgt INT ( 10 ) NOT NULL
) ;

现在可以用一句SQL查询得到整个树的节点:

SELECT * FROM category WHERE lft BETWEEN 1 AND 14 ORDER BY lft ASC

在本SQL中的两个数字值1和14就是根节点的left和right值。类似的如果想得到某个节点的所有孩子节点,只需要将该SQL语句的1和14替换成本节点的left和right值就可以了。例如,如果想得到所有男人的衣服,可以用下面的SQL语句:

SELECT * FROM category WHERE lft BETWEEN 2 AND 7 ORDER BY lft ASC

想找到一条到某个节点的路径,用一条SQL语句就可以搞定:

SELECT * FROM category WHERE lft < 9 AND rgt > 10 ORDER BY lft ASC

请仔细观察一下一些叶子节点到根节点的路径。就会发现所有的祖先都有更小的左值和更大的右值。本例子中一条到裙子分类的路径被取出。观察一下裙子的所有left值都小于9,right值都大于10,其他的非祖先节点都不满足该要求。

尽管嵌套集合模型更加复杂并且有些难以理解,它有非常多的优势。它不需要依赖其他资源(比如PHP代码),也不需要递归。同时,数据库查询语句非常的简单,大多数用一条SQL语句就可以搞定。这些特性都能够显著的增加应用程序的性能,使得它能够用可接受的速度来处理复杂的层级结构。

然而万事皆无完美,更新该层级结构(增加或删除节点)却更加的复杂,并且可能会非常慢。

增加一个节点到层级结构的方法:

将一个节点插入到层级数据中,需要整个树很多节点的left和right值的更新。例如,如果你想将一个男士运动鞋的分类插入到男性衣服的短裤后面。那么所有你必须将大于6的所有left和right值都增加2。为什么呢?因为短裤的right值是6,那你就必须将你的新分类的left和right值设定为7和8,当然,以下两条SQL就可以解决:

​UPDATE category SET rgt=rgt+2 WHERE rgt>6​

​UPDATE category SET lft=lft+2 WHERE lft>6​


现在树中间已经有空隙用来插入新分类了,用一条SQL插入该节点:


INSERT INTO category (category_name,lft,rgt) VALUES ( 'Sneakers' , '7' , '8' )

树形层级数据中删除一个节点的方法:


在层级集合模型中删除一个节点的方法,比在邻接表中相同的操作稍微难一些。不同的操作的复杂程度是不同的,比如删除一个叶子节点和一个带孩子节点就很不同。



要删除一个叶子节点,先将所有left和right大于该节点left和right值的节点的left和right减去2,然后再删除该节点。以下使用SQL实现该过程:



​UPDATE category SET lft=lft-2 WHERE lft>5​

​UPDATE category SET rgt=rgt-2 WHERE rgt>6​

​DELETE FROM category WHERE lft=​​​ ​​​'5'​​​ ​​​AND rgt=​​​ ​​​'6'​



该例子中短裤节点被删除了。


如果要删除的节点有孩子节点的话,删除过程会多一个步骤:



比如我们删除男性衣服分类的时候:

​UPDATE category SET lft=lft-1, rgt=rgt-1 WHERE lft>2 AND rgt<7​

​UPDATE category SET lft=lft-2 WHERE lft>7​

​UPDATE category SET rgt=rgt-2 WHERE rgt>7​

​DELETE FROM category WHERE lft=​​​ ​​​'2'​​​ ​​​AND rgt=​​​ ​​​'7'​




哪种模型对于处理树形分层数据更好?

哪种情况更好呢,看情况。如果需要一个更加灵活的模型,更容易更新,就用邻接表模型吧。如果分类构成了一棵复杂的数,并且更新不需要很频繁,用嵌套集合模型肯定是上上之选。

本文内容翻译自:​​访问​​,其中原文中的代码有些问题,本人添加了测试数据并改正了代码。

标签:category,rgt,树状,--,id,lft,MySQL,PHP,节点
From: https://blog.51cto.com/peishuai/6042088

相关文章

  • PHP创建和解析JSON数据的方法
    JSON可以解释为“JavaScript的对象表示方法”,也就是说JSON的概念是来源于JavaScript的,对于WEB开发模式来说,下面这个图大家肯定很熟悉:​​​​由此可以看到,客户端浏览器和服......
  • PHP开发者最好的学习资源收集
    以下内容翻译自:​​phpbuilder.com​​​的​​PHPDeveloperResources​​​文章。资源全为英文资料,不过英文很多文章确实写的非常好!当今PHP是开发WEB应用程序最流行的脚......
  • PHP对数组的高级遍历和操作处理方法
    PHP对数组的处理可以称为该语言最有吸引力的特性之一,它支持70多种数组相关的函数。不论你想翻转一个数组、判断某个值在数组中是否存在、将数组转换成一个字符串还是计算数......
  • 使用PHP连接、操纵Memcached的原理和教程
    Memcahced开源分布式内存对象缓存系统通过减少数据库的负担,从而能够加速你的web应用。在本文中我将解释怎样实现一个基于Memcahced的缓存系统。数据库实例中使用的数据库表......
  • PHPMyWind支持PDF粘贴
    ​ 项目需求可发布文章需求涉及到富文本编辑器经过查阅我选择了较为简便不需要后端支持可独立完成的tinymce框架官方文档也是相当完整虽然都是全英文但是有强大的......
  • PHP远程操纵WordPress的方法(流程剖析)
    一直想写一个给wordpress群发文章的应用,这样我就能自动采集文章,然后写个脚本自动发送文章了,哈哈。虽然用python干这种事情貌似更加擅长,但是我想做个web界面访问该应用,而pyth......
  • 国外10个非常有趣的PHP博客
    ​​DavidWalshblog​​​​​​DavidWalsh是一个非常有经验的PHP开发者,他参与了很多大型的项目。在他的博客中你能发现几乎每天的对PHP开发的建议。​​Roshan’sblog......
  • PHP数组使用、特性、函数的总结
    包含其他数组的数组成为多维数组关联数组(即非数字索引数组)使用更有意义的数据作为数组的索引1、数组索引数组$products = array(‘a’, ‘b’, ‘c’);//就像一个......
  • 新浪微博的mid转换成base62格式的PHP函数
    最近有用到将新浪微博的mid地址转换成短格式编码的方法,例如:221110410216147026转换后:zF4mOFpN7A比如新浪微博地址可以组装成这样:​​http://weibo.com/1642634100/5en0UftjV......
  • MySQL8.0修改数据库名的方法
    注:1、不管存储引擎是InnoDB还是MyISAM直接去修改data下的文件夹名称都是不可行的;2、renamedatabaseoldnameTOnewname;也是不可行的;可采用如下方法:方法一整体思路:新......