一、概述
插入、更新、删除和移动操作总是保证结构完好;查询使用一条 SELECT 语句即可;查询节点的所有父节点使用 CET 进行递归查询。其中最复杂的部分是更新和移动。
概念 | 说明 |
---|---|
节点 | 对应一条记录。 |
父节点 | 节点的 ParentID 对应的记录。 |
顶级节点 | 当 ParentID 为NULL 时,为顶级节点。 |
节点树 | 节点及其所有直接和间接子节点形成的树状结构。从数据库角度上看为若干条记录。其中只有顶级父节点的 ParentID 为 NULL ,非顶级父节点的 ParentID 不为 NULL。 |
层级 | Level。顶级节点的 Level 为1,以此类推。 |
显示序号 | DisplayOrder。从1开始计数。 |
二、表结构
字段名 | 数据类型 | 主键 | 可NULL | 备注 |
---|---|---|---|---|
GroupID | uniqueidentifier | √ | 分类ID | |
ParentID | uniqueidentifier | √ | 外键 | |
Name | nvarchar(50) | 分类名称 | ||
Level | int | 层级。从1开始。 | ||
DisplayOrder | int | 显示顺序。从1开始。 |
MS SQL Server 表创建脚本:
1 | CREATE TABLE [dbo].[Group]( |
为 Level 和 DisplayOrder 字段增加索引。
三、插入( INSERT )
如果作为顶级节点插入( ParentID 为 NULL ),直接将记录添加至最后即可;否则:
1. ParentID
新节点的父节点为 ParentID 对应的节点
2. Level
新节点的 Level 为父节点的 Level + 1
3. DisplayOrder
新节点作为父节点的最后一个节点,其 DisplayOrder 为父节点当前最后一个子节点的 DisplayOrder 增 1。 如果父节点还没有子节点,则 DisplayOrder 为父节点的 DisplayOrder 增 1 ;(换个角度说,新节点的 DisplayOrder,为父节点之后与之同级( Level ) 或层级更小的第一个节点的 DisplayOrder 。如果不存在这样的节点,说明”父节点树”之后没有任何节点了,即为当前所有记录的数量增1。)
4. 在”父节点树”之后的所有节点的 DisplayOrder 增 1
另一种更简单的实现是新节点直接作为父节点的第一个节点,则只需将父节点之后的 DisplayOrder 增 1 即可。
四、删除( DELETE )
- 如果预删除节点包含子节点,不允许删除(当然,也可以设计成删除子树,但目前没这样实现);
- 比预删除节点的 DisplayOrder 大的节点的 DisplayOrder 减 1;
- 删除节点。
1 | UPDATE [Group] SET [DisplayOrder] = [DisplayOrder]-1 WHERE [DisplayOrder] > @DisplayOrder; |
五、移动 (MOVE)
以另一个节点为目标,移到目标节点的上方或下方。如果移动到上方,则作为目标节点的兄弟节点;如果移动到下方,还可选择是作为兄弟节点还是子节点。
如果要移动到目标节点上方,作为另一个节点的子节点呢?那就选择另一个节点作为目标节点吧。
六、更新( UPDATE )
如果仅更新 Name 之类的不影响结构的字段很简单,直接 UPDATE 即可。否则包括这三种情况:
1. 顶 -> 子
从顶级节点移动到某个节点下作为其子节点。
要区分将导致节点上移还是下移,相应的也会影响节点和父节点之间的节点的 DisplayOrder 。
2. 子 -> 顶
从子节点升级为顶级节点。
节点会成为最后一个顶级节点。如果不是想要的顺序,再移动即可。
3. 子 -> 子
作为子节点切换父节点。
同样,要区分将 导致节点上移还是下移,相应的也会影响节点和父节点之间的节点的 DisplayOrder 。
更新节点删除该节点再新增节点——当然不会这样做,因为这样比较低效(也可接受),并且节点可能和其他表的记录有关联。
七、查询节点路径( CET )
1 | WITH CET AS |
八、查询全部( SELECT )
1 | SELECT GroupID, ParentID, [Level], DisplayOrder FROM [Group] ORDER BY DisplayOrder |
九、节点树 ( SELECT )
1. 先查询出本节点,主要使用其 Level 和 DisplayOrder
2. 查询出本节点的下一个兄弟节点,假设命名为 DisplayOrderNext
1 | SELECT DisplayOrder AS DisplayOrderNext FROM [Group] WHERE DisplayOrder > @DisplayOrder AND [Level] <= @Level ORDER BY DisplayOrder |
3. 如果 DisplayOrderNext 为 NULL,则本节点无兄弟节点且无 Level 更低的节点,一查到底即可。否则见4
1 | SELECT GroupID, ParentID, [Level], DisplayOrder FROM [Group] WHERE DisplayOrder >= @DisplayOrder ORDER BY DisplayOrder |
4. 如果 DisplayOrderNext 不为 NULL
1 | SELECT GroupID, ParentID, [Level], DisplayOrder FROM [Group] WHERE DisplayOrder >= @DisplayOrder AND DisplayOrder < @DisplayOrderNext AND [Level] >= @Level ORDER BY DisplayOrder |