构建具有相关聚合的子树

时间:2011-04-28 15:47:28

标签: sql-server sql-server-2005 tree common-table-expression recursive-query

我为这个模糊的标题道歉。我想不出如何最好地总结这个问题。我有一个分层表(例如,ID int, ParentID int),需要为ID生成一个子树。这通过递归CTE来完成。困难在于,对于每个节点,我需要计算一组相应值的运行按位OR,然后使用与父节点相同的值得到的bit-OR。这意味着每个节点都继承其父节点的位掩码,并可以设置自己的附加位。我可以使用OUTER APPLY和我要求的earlier question中提到的技术在CTE的锚成员中计算此值。不幸的是,我无法在CTE的递归部分以相同的方式计算它,因为它使用SUM并且不允许聚合。

有没有办法重组这个以做我想做的事情?

declare @ID int
set @ID = 1

;with _Bits_(RowNum, BitMask) as
(
  select
    1,
    1
  union all select
    RowNum + 1,
    BitMask * 2
  from
    _bits_
  where
    RowNum < 31
),
_Tree_ as
(
  select
    a.ID,
    a.ParentID,
    b.BitMask
  from
    Tree a
    outer apply
    (
      select
        sum(distinct y.BitMask) as BitMask
      from
        BitValues x
        inner join _Bits_ y
          on (x.Value & y.BitMask) <> 0
      where
        x.ID = a.ID
    ) b
  where
    a.ID = @ID
  union all select
    a.ID,
    a.ParentID,
    c.BitMask | b.BitMask
  from
    Tree a
    inner join _Tree_ b
      on b.ID = a.ParentID
    outer apply
    (
      select
        sum(distinct y.BitMask) as BitMask
      from
        BitValues x
        inner join _Bits_ y
          on (x.Value & y.BitMask) <> 0
      where
        x.ID = a.ID
    ) c
)
select * from _Tree_

修改

如果它有助于概念化问题:层次结构很像目录结构,并且位掩码就像从父文件夹继承的权限。

示例数据

create table Tree (ID int primary key, ParentID int null foreign key references Tree (ID))

insert Tree values (1, null)
insert Tree values (2, 1)
insert Tree values (3, 1)

create table BitValues (ID int not null foreign key references Tree (ID), BitMask int not null)

insert BitValues values (1, 1)
insert BitValues values (2, 2)
insert BitValues values (2, 4)
insert BitValues values (3, 8)
insert BitValues values (3, 16)
insert BitValues values (3, 32)

对于@ID 1,我希望查询返回:

+----+----------+---------+
| ID | ParentID | BitMask |
+----+----------+---------+
|  1 |   NULL   |       1 |
|  2 |        1 |       7 |
|  3 |        1 |      57 |
+----+----------+---------+

2 个答案:

答案 0 :(得分:0)

declare @ID int;
set @ID = 1;

with extrarows as
(
   select t.id, null as parent, v.BitMask as total
   from tree t
   join BitValues v on t.id = v.id
   where t.id = @ID

   union all 

   select t.id, r.id, v.BitMask | r.total
   from extrarows r
   join Tree t on r.id = t.parentid
   join BitValues v on t.id = v.id
)
select id, parent, 
  MAX(total & 1) +
  MAX(total & 2) +
  MAX(total & 4) +
  MAX(total & 8) +
  MAX(total & 16) +
  MAX(total & 32) +
  MAX(total & 128) +
  MAX(total & 256) +
  MAX(total & 512) +
  MAX(total & 1024) +
  MAX(total & 2048)  -- more if you want em.
     as BitMask 
from extrarows   
group by id, parent

一些注意事项:

  • 我假设传入的@id是树的“根”。 (如果这不符合您的需要,请随意爬上树以找到根的起始位掩码。)

  • 虽然对MAX位进行求和确实有效,但对于许多记录中的大位字符串可能不具备高效性。我不知道你有多少比特但它不到16左右应该没问题 - 比如听听你的发现。

  • 要提高性能,请切换到自定义C#聚合。

答案 1 :(得分:0)

Hogan答案的细微改进(IMO):

declare @ID int;
set @ID = 1;

with _Bits_(RowNum, BitMask) as
(
  select
    1,
    1
  union all select
    RowNum + 1,
    BitMask * 2
  from
    _bits_
  where
    RowNum < 31
),
extrarows as
(
   select t.id, null as parent, v.BitMask as total
   from tree t
   join BitValues v on t.id = v.id
   where t.id = @ID

   union all 

   select t.id, r.id, v.BitMask | r.total
   from extrarows r
   join Tree t on r.id = t.parentid
   join BitValues v on t.id = v.id
)
select a.id, a.parent, sum(distinct y.BitMask) as BitMask
from extrarows a
  inner join _Bits_ y
    on (a.total & y.BitMask) <> 0  
group by a.id, a.parent