<?php
/*
*树形结构数据库实例
*两种方式
邻接表实现方式
1、从节点到根节点的方式输出
2、从根节点到子节点的方式输出
邻接表结构的缺点:
1、每条记录存储父节点。
2、查询的过程中需要使用递归等这种很消耗资源的查询方式。
3、删除某个节点时,其对应的子节点在以后的查询过程中也会消失
嵌套集合模型用于树形分层结构数据
嵌套集合模型,也叫做先根遍历树算法,也是一种处理树形层级数据的方法。
代替节点间的父子关系,层级使用嵌套的容器的集合来表示,其中每个节点具有两个值,一个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
);
*/
//输出从节点到跟节点的路径
function get_path($category_id)
{
$con = mysql_connect("localhost","root","root");
if (!$con) {
die('数据库连接失败: ' . mysql_error());
}
mysql_select_db('tree', $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']. '>';
}
//方法二,从根节点输出节点名称
function display_children($category_id, $level)
{
$con = mysql_connect("localhost","root","root");
if (!$con) {
die('数据库连接失败: ' . mysql_error());
}
mysql_select_db('tree', $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);
/*嵌套表结构解释说明
现在可以用一句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'
哪种模型对于处理树形分层数据更好?
哪种情况更好呢,看情况。如果需要一个更加灵活的模型,更容易更新,就用邻接表模型吧。如果分类构成了一棵复杂的数,并且更新不需要很频繁,用嵌套集合模型肯定是上上之选。
*/
/*
*树形结构数据库实例
*两种方式
邻接表实现方式
1、从节点到根节点的方式输出
2、从根节点到子节点的方式输出
邻接表结构的缺点:
1、每条记录存储父节点。
2、查询的过程中需要使用递归等这种很消耗资源的查询方式。
3、删除某个节点时,其对应的子节点在以后的查询过程中也会消失
嵌套集合模型用于树形分层结构数据
嵌套集合模型,也叫做先根遍历树算法,也是一种处理树形层级数据的方法。
代替节点间的父子关系,层级使用嵌套的容器的集合来表示,其中每个节点具有两个值,一个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
);
*/
//输出从节点到跟节点的路径
function get_path($category_id)
{
$con = mysql_connect("localhost","root","root");
if (!$con) {
die('数据库连接失败: ' . mysql_error());
}
mysql_select_db('tree', $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']. '>';
}
//方法二,从根节点输出节点名称
function display_children($category_id, $level)
{
$con = mysql_connect("localhost","root","root");
if (!$con) {
die('数据库连接失败: ' . mysql_error());
}
mysql_select_db('tree', $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);
/*嵌套表结构解释说明
现在可以用一句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'
哪种模型对于处理树形分层数据更好?
哪种情况更好呢,看情况。如果需要一个更加灵活的模型,更容易更新,就用邻接表模型吧。如果分类构成了一棵复杂的数,并且更新不需要很频繁,用嵌套集合模型肯定是上上之选。
*/