我们经常需要在关系型数据库中保存一些树状结构数据,比如分类、分组、权限控制 ACL、论坛帖子树状回复等等。常用的方法有两种,一种是领接表的方式,另一种是预排序遍历树方式。
本文以 MySQL 和 PHP 为例进行说明,假设树状结构如下:
领接表方式
领接表方式主要依赖于一个 parent 字段,用于指向上级节点,将相邻的上下级节点连接起来,从而将整个树的各个节点连接起来。这样,我们例子中的节点变成行记录如下:
id 为自动递增自动,parent_id 为上级节点的 id。一目了然,“PHP”是“Language”的子节点。
接下来我们要显示树,PHP 代码也可以很直观,显示树的子节点的代码如下:
<?php
/**
* @param $parent_id 父节点 id,0 则显示整个树结构。
* @param $level 当前节点所处的层级,用于缩进显示节点。
*/
function show_children($parent_id = 0, $level = 0)
{
// 获取父节点下的所有子节点
$result = mysql_query('SELECT id, name FROM tree WHERE parent_id='.intval($parent_id));
// 显示每个子节点
while ($row = mysql_fetch_array($result)) {
// 缩进显示
echo '<div style="margin-left:'.($level*12).'px">'.$row['name'].'</div>';
// 递归调用当前函数,显示再下一级的子节点
show_children($row['id'], $level+1);
}
}
?>
想要显示整个树结构,调用 show_children()。想要显示“Database”子树,则调用 show_children(2),因为“Database”的 id 是 2。
还有一个经常用到的功能是获取节点路径,即给出一个节点,返回从根节点到当前节点的路径。用函数实现如下:
<?php
/**
* @param $id 需要获取路径的当前节点的 id。
*/
function get_path($id)
{
// 获取当前节点的父节点 id 和当前节点名
$result = mysql_query('SELECT parent_id, name FROM tree WHERE id='.intval($id));
$row = mysql_fetch_array($result);
// 使用此数组保存路径
$path = array();
// 将当前节点名保存进路径数组中
$path[] = $row['name'];
// 如果父节点非 0,即非根节点,则进行递归调用获取父节点的路径
if ($row['parent_id']) {
// 递归调用,获取父节点的路径,并且合并到当前路径数组的其它元素前边
$path = array_merge(get_path($row['parent_id']), $path);
}
return $path;
}
?>
想要获取“MySQL 5.0”的路径,调用 get_path(4),4 即是这个节点的 id。
领接表方式的优点在于容易理解,代码也比较简单明了。缺点则是递归中的 SQL 查询会导致负载变大,特别是需要处理比较大型的树状结构的时候,查询语句会随着层级的增加而增加,WEB 应用的瓶颈基本都在数据库方面,所以这是一个比较致命的缺点,直接导致树结构的扩展困难重重。