数据库无限层级结构左右值算法

数据库无限层级结构模型又叫嵌套集模型(Nested Set Model)是一种用于表示树状结构的数据模型,它通过左值和右值来表示树中节点的层次关系。Joe Celko在20世纪70年代首次提出了这种模型并在后来写入他的的著作《SQL for Smarties》(中文版翻译为《SQL权威指南》)中,在后来的数据库设计中得到了广泛普及和应用。

左右值的定义

在嵌套集模型中,每个节点都有两个值,即左值(Left Value)和右值(Right Value)。左值表示节点在树中的进入顺序,右值表示节点在树中的离开顺序。通过这两个值,可以确定每个节点的子树范围、父子关系、兄弟关系等信息。

其中每个节点的 right_id - left_id = 2 * 子节点数 + 1。

一个节点的子节点满足条件 child.left_id > parent.left_id and child.right_id < parent.right_id。

特点和优势

快速查询:通过左值和右值,可以快速确定节点的父子关系、兄弟关系等信息,从而加快了树状结构的查询速度。

空间效率:相比其他树状结构表示方法(如邻接列表、路径枚举等),嵌套集模型通常需要更少的存储空间。

简单的操作:对于嵌套集模型的节点插入、删除和移动等操作,通常只需要更新左右值,而不需要对整个树进行重组。

注意事项

更新代价:当节点插入、删除或移动时,可能需要更新树中大量节点的左值和右值,这可能会增加更新的代价。

不适用于频繁修改的树:由于更新代价较高,嵌套集模型不适用于频繁修改的树状结构,适用于静态或者修改较少的树。

SQL实例

  • 求某节点下面有多少个子孙节点:
sql 复制代码
select @left := left_id, @right := right_id from tree where id=?;
select (@right - @left -1) / 2 from tree;
  • 求某节点(node)下面所有子孙节点:
sql 复制代码
select @left := left_id, @right := right_id from tree where id=?;
select * from tree where left_id>@left and right_id<@right order by left_id;
  • 求某节点所有父祖节点:
sql 复制代码
select @left := left_id, @right := right_id from tree where id=?;
select * from tree where left_id<@left and right_id>@right order by left_id;
  • 在某节点(parent)下新增一节点(做为parent幼子):
sql 复制代码
select @parent_right := right_id, @parent_id := id from tree where id=$parent_id;
update tree set right_id = right_id + 2 where right_id >= @parent_right;
update tree set left_id = left_id + 2 where left_id >= @parent_right;
insert into tree( name, parent_id, left_id , right_id ) values (@name, @parent_id, @parent_right, @parent_right + 1);
  • 在某节点(parent)下新增一节点(做为parent长子):
sql 复制代码
select @parent_left := left_id, @parent_id := id from tree where id=$parent_id;
update tree set right_id = right_id + 2 where right_id > @parent_left ;
update tree set left_id = left_id + 2 where left_id > @parent_left ;
insert into tree( name, parent_id, left_id , right_id ) values (@name, @parent_id, @parent_left + 1, @parent_left + 2);
  • 在某节点(brother)同级左侧增加一节点(兄):
sql 复制代码
select @brother_left := left_id, @brother_right := right_id, @parent_id := parent_id from tree where id = $brother_id;
update tree set right_id = right_id + 2 where right_id >= @brother_right;
update tree set left_id = left_id + 2 where left_id >= @brother_left;
insert into tree ( name, parent_id, left_id , right_id ) values ( @name, @parent_id, @brother_left,@brother_right );
  • 在某节点(brother)同级右侧增加一节点(弟):
sql 复制代码
select @brother_left := left_id, @brother_right := right_id , @parent_id := parent_id from tree where id = $brother_id;
update tree set right_id = right_id + 2 where right_id > @brother_right;
update tree set left_id = left_id + 2 where left_id > @brother_left;
insert into tree ( name, parent_id, left_id , right_id ) values ( @name, @parent_id, @brother_right + 1, @brother_right + 2 );
  • 删除某子节点(node)及下面孙节点:

|--------------------------------------------------------------------------------|--------------------------------------------------------------------------------|
| | |

sql 复制代码
select @left := left_id, @right := right_id from tree where id = ?;
delete from tree where left_id >= @left and right <= @right;
update tree set left_id = left_id - (@right - @left + 1)  where left_id > @right;
update tree set right_id = right_id - (@right - @left + 1)  where right_id > @right;

删除某子节点(node),但不删除孙节点(孙节点上移一级):

|--------------------------------------------------------------------------------|--------------------------------------------------------------------------------|
| | |

sql 复制代码
select @left := left_id, @right := right_id, @parent_id := parent_id, @node_id=id from tree where id = ?;
-- 删除自身节点
delete from tree where left_id = @left and right = @right;
-- 或者 delete from tree where id = ?;
 
-- 调整孙节点的左右值(层级升级)
update tree set left_id = left_id - 1, right_id = right_id - 1 where left_id > @left and right_id < @right;
 
-- 调整原节点右侧所有节点的左右值(腾出空间)
update tree set left_id = left_id - 2  where left_id > @right;
update tree set right_id = right_id - 2  where right_id > @right;
-- 更新直接子节点的parent_id
update tree set parent_id = @parent_id  where parent_id  > @node_id;
  • 移动某节点及下面子孙节点到另一节点下面:

sql 复制代码
-- 获取节点A的左右边界值和层级
select @left_bound := left_id,
       @right_bound := right_id,
       @level := right_id - left_id + 1
from tree where id = 'A';
 


-- 获取节点B的右边界值
select @target_right := right_id from tree where id = 'B';


-- 计算节点A的移动距离
set @distance = @target_right - @left_bound;


-- 更新节点A及其子节点的左右边界值和层级
update tree
set left_id = case
              when left_id between @left_bound and @right_bound then left_id + @distance
              else left_id
           end,
    right_id = case
               when right_id between @left_bound and @right_bound then right_id + @distance
               else right_id
            end
where right_id >= @left_bound;

-- 更新节点A的父节点为节点B (如果有parent_id字段)
update tree set parent_id = 'B' where id = 'A';