如何在SQL Server中有效地合并两个层次结构?

时间:2011-08-14 17:02:34

标签: sql sql-server sql-server-2008 hierarchical-data hierarchyid

我有两个带有hierarchyid字段的表,其中一个是具有需要合并到另一个的新数据的临时表(即,需要添加到主树的一组节点,其中一些可能已经存在了。)

除了定义树结构(父/子关系)的hierarchyid列之外。每个表都有一个单独的列,其中包含唯一标识每个节点的节点标识符。也就是说,判断登台表中的节点是否已经在主表中的方法是通过节点ID,而不是通过hierarchyid列。

势必需要,需要执行的处理看起来像这样:

For each row, RS, in the staging table:
    If there is not already a row with the same Id as RS in the main table:
         Find the parent, PS, of the staging row
         Find the row, PM, in the main table that has the same node ID as PS
         Create a new child, RM of row PM
         Set PM's ID equal to the ID of RS

重要的是,这种方法只有在登台表中的树以广度优先顺序排序/遍历时才有效 - 这样当遇到RS时,可以保证其父PS在其中已经有相应的行。主要表。

到目前为止,我在SQL服务器中看到实现此目的的唯一方法是在登台表(已经排序)上使用游标,并为每一行调用一个存储过程,基本上完全按照上述描述完成,完成使用SELECT MAX()查找已作为PM的子项存在的最高hierarchyid,以便可以唯一地添加子项。

这是一种非常低效的方法,但是对于我的目的来说太慢了。还有更好的办法吗?

对于背景,这是我正在进行的一种可行性检查。我需要弄清楚我是否可以在SQL Server中快速执行此操作。如果事实证明我不能在数据库之外以另一种方式做到这一点。树的合并是(实际上,在某种意义上)问题域的固有特性,因此以不同方式构造数据或采用更广泛的视图并试图以某种方式避免完全执行此操作一个选项。

更新

根据要求,这是一个具体的例子。

表“staging”和“main”都有相同的两列:

   hierarchy_id of type hierarchyid
   node_id of type bigint

初始内容

主:

 hierarchy_id    node_id
 /1/             1
 /1/1/           2
 /1/2/           3
 /1/3/           4

分期:

 hierarchy_id    node_id
 /1/             1
 /1/1/           3
 /1/2/           5
 /1/1/1/         6

所需内容

主:

 hierarchy_id    node_id
 /1/             1
 /1/1/           2
 /1/2/           3
 /1/3/           4
 /1/4/           5
 /1/2/1/         6

请注意,使用hierarchy_id / 1/1 /的临时表中的节点对应于目标表中的hiearchy_id / 1/2 /的节点(这就是node_id很重要的原因 - 不能只复制hierarchy_id值) 。另请注意,具有node_id 6的新节点被添加为正确父节点的子节点,具有node_id 3的子节点,这就是hierarchy_id很重要的原因 - 它定义了任何新节点的树结构(父/子关系)。任何解决方案都需要考虑到这两个方面。

3 个答案:

答案 0 :(得分:3)

我们一直在研究需要类似解决方案的产品。经过对此方法和其他方法的大量研究后,我们得出结论,hierarchyID方法不适合我们。

所以直接回答你的问题:使用这种方法没有更好的方法来做到这一点。

查看Nested Set ModelsAdjacency List Model

对于这一特定的设计挑战,这两者都是更优雅,更有效的解决方案。

修改: 作为一个经过深思熟虑后,你没有与SQL结合 - 使用非关系数据库可以更好地解决这个问题。 我们不能这样,因为没有人在设计非关系数据库方面有足够的专业知识,但是如果SQL是可选的,那么你可以在MongoDB中以更好,更有效的方式使用当前的方法。

答案 1 :(得分:3)

以这种方式对层次结构建模会导致问题。 hierarchy_id列违反了第一范式,如果您没有序列化/瓶颈访问,合并过程将容易更新异常。

你应该考虑一个只有node_id和parent_id的表,看看它如何使你的合并问题变得无足轻重

node_id   parent_id
1         NULL
2         1
3         2
4         3

node_id   parent_id
1         NULL
3         1
5         2
6         1

您将使用递归查询,您可能会对执行计划的效率感到惊讶。如果必须具有展平的层次结构列,则可以使用递归查询创建索引视图。

答案 2 :(得分:0)

以下是一种解决方案,可以将行从源@S移动到目标@T一次一个级别。为了简化一点,我添加了一个根节点,以便始终在创建新的HierarcyID时使用父节点。

我从来没有使用过HierarchyID,所以绝对有更有效的方法来做到这一点,但它至少应该比一次做一行更有效。

-- Target table
declare @T table 
(
  hierarchy_id hierarchyid primary key,
  node_id bigint
)

insert into @T values
('/',             0), -- Needed for simplicity
('/1/',           1),
('/1/1/',         2),
('/1/2/',         3),
('/1/3/',         4)

-- Source table
declare @S table 
(
  hierarchy_id hierarchyid primary key,
  node_id bigint
)

insert into @S values
('/',               0),
('/1/',             1),
('/1/1/',           3),
('/1/2/',           5),
('/1/1/1/',         6)

declare @lvl int = 1

-- Move rows from @S to @T for each level
while exists(select *
             from @S
             where hierarchy_id.GetLevel() = @lvl)
begin

  insert into @T
  select T.hierarchy_id.GetDescendant(C.MaxID, null),
         S.node_id
  from (select S1.node_id, S2.node_id as ParentID
        from @S as S1
          inner join @S as S2
            on S1.hierarchy_id.GetAncestor(1) = S2.hierarchy_id
        where S1.hierarchy_id.GetLevel() = @lvl and
              S1.node_id not in (select node_id
                                 from @T)
       ) as S
    inner join @T as T
      on S.ParentID = T.node_id
    outer apply (select max(hierarchy_id) as MaxID
                 from @T as T2
                 where T.hierarchy_id = T2.hierarchy_id.GetAncestor(1)) as C       

    set @lvl = @lvl + 1
end

select *, hierarchy_id.ToString()
from @T
where hierarchy_id <> hierarchyid::GetRoot()  

结果:

hierarchy_id  node_id  (No column name)
------------  -------  ----------------
0x58          1        /1/
0x5AC0        2        /1/1/
0x5B40        3        /1/2/
0x5B56        6        /1/2/1/
0x5BC0        4        /1/3/
0x5C20        5        /1/4/