1. 问题描述
树形结构用途广泛,不可避免地要用到树结点的移动,如果按照数据库规范来设计的话,对于批量移动结点时, 对所移动结点的子孙结点是没有影响的,因为子结点只要有它的父结点的ID就可以了,但是如果要查询某结点的所有子孙结点时,这种设计在性能上就很难满足,通常要用递归才能完成。为了方便查询,在表结构中多添加一个字段Tree_AncestorsId ,用于存放祖先结点,这样, 查询某结点E的所有子孙结点时,只要在查找 Tree_AncestorsId 中存在 /E/ 内容的结点,就是所要查找的结点.此设计方便了查询,但给结点的移动带来麻烦:移动一个结点时,此结点下所在子结点的Tree_AncestorsId 都要修改.一般的做法是用递归的方法更新所有子点的Tree_AncestorsId 字段,这样更新一个节点的代价很高, 经过摸索,有简单的方法可以达到要求,就是下面要描述的内容。
2. 结点E移动前后结构如下所示
3. 数据库存储结构如下表所示:
列名 | 类型 | 备注 |
Tree_Id | Int (4) | 主键 |
Tree_Name | Nvarchar(20) | 结点名 |
Tree_ParentId | Int | 父结点Id |
Tree_AncestorsId | Varchar(4000) | 祖先结点Id,包括父结点 |
... |
|
|
表1
4. 解决方法:
移动E结点到F结点下时:
1).E结点的Tree_ParentId和Tree_AncestorsId 要修改。
2).E结点的所有子孙结点(G,H,I,J)的Tree_ParentId 不变,但 Tree_AncestorsId都要修改。
3).G,H,I,J 的 Tree_AncestorsId 修改时,可以将字段中 /A/B/E/.. 修改成/A/B/D/F/E/..,因为 /E/后面的内容不用修改,看下表所示.
4).移动前结点字段数据
Tree_Id | Tree_ParentId | Tree_AncestorsId |
... |
|
|
B | A | /A/ |
E | B | /A/B/ |
G | E | /A/B/E/ |
H | E | /A/B/E/ |
I | H | /A/B/E/H/ |
J | I | /A/B/E/H/I/ |
表2
5).移动后结点字段数据
Tree_Id | Tree_ParentId | Tree_AncestorsId |
... |
|
|
B | A | /A/ |
E | F | /A/B/D/F/ |
G | E | /A/B/D/F/E/ |
H | E | /A/B/D/F/E/ |
I | H | /A/B/D/F/E/H/ |
J | I | /A/B/D/F/E/H/I/ |
表3
6).对比表2和表3,红色字体表示结点移动后,内容发生了改变,蓝色字体表示结点移动后内容没有变化,因此, 移动E结点到F结点下时,将E结点的Tree_ParentId修改为F ,将Tree_AncestorsId修改为 /A/B/D/F/, E结点的子孙结点(G,H,I,J)只须修改Tree_AncestorsId 字段,对应的SQL语句如下:
--=============================================
-- Author: yufei
-- Createdate: 2010-01-05
--Description: 批量移动树结点
--=============================================
CREATE PROCEDURE [dbo].[SP_Tree_Move]
@Tree_Id int,
@Tree_ParentId int
AS
BEGIN
/*存放所移动结点的父结点的Tree_AncestorsId值*/
declare @AncestorsId nvarchar(4000)
/*为@AncestorsId赋值*/
select @AncestorsId=Tree_AncestorsId fromDDPMDemo_T_Sales_Tree where Tree_Id=@Tree_ParentId
/*修改第一个结点*/
Update DDPMDemo_T_Sales_Tree set Tree_ParentId=@Tree_ParentId,
Tree_AncestorsId=@AncestorsId+convert(varchar,@Tree_ParentId)+'/'
where Tree_Id=@Tree_Id
/*声明存放所移动结点E 移动后的祖先Tree_AncestorsId值*/
DECLARE @ANSTRING VARCHAR(500)
/*声明要查询的内容,形如'%/E/%' */
DECLARE @REGSTR VARCHAR(500)
/*为@REGSTR赋值,形如'%/E/%' */
SET @REGSTR='%/'+convert(varchar,@Tree_Id)+'/%'
/*为@ANSTRING赋值*/
SET @ANSTRING=@AncestorsId+convert(varchar,@Tree_ParentId)+'/'
/*更新所移动结点E 的所有子孙结点的Tree_AncestorsId值*/
Update DDPMDemo_T_Sales_Tree
set Tree_AncestorsId=@ANSTRING+SUBSTRING(Tree_AncestorsId,PATINDEX(@REGSTR,Tree_AncestorsId)+1,LEN(Tree_AncestorsId))
WHERE PATINDEX(@REGSTR,Tree_AncestorsId)>0
END