【问题标题】:Building sub-tree with correlated aggregate使用相关聚合构建子树
【发布时间】:2011-08-14 20:23:51
【问题描述】:

对于含糊的标题,我深表歉意。我想不出如何最好地总结这个问题。我有一个分层表(例如ID int, ParentID int),需要为ID 生成一个子树。这可以通过递归 CTE 轻松完成。困难在于,对于每个节点,我需要计算一组相应值的运行按位或,然后对父节点计算得到相同值的位或。这意味着每个节点都继承其父节点的位掩码,并且可以设置自己的附加位。我可以使用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 |位掩码 | +----+----------+---------+ | 1 |空 | 1 | | 2 | 1 | 7 | | 3 | 1 | 57 | +----+----------+---------+

【问题讨论】:

  • 你能给我们一些示例数据吗?
  • @Hogan:我用测试数据更新了问题。
  • 我知道如何解决这个问题——我应该可以在几个小时内发帖——必须做通勤的事情。
  • @Hogan - 酷。我期待看到你的想法。我也想出了一个解决方案,但它是 lot 的代码。我不能发布它,因为它包含机密数据,而且清理起来太多了。如果您或其他人在接下来的一两天内没有发布答案,我可能会发布我采取的一般步骤。

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


【解决方案1】:
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# 聚合。

【讨论】:

  • 谢谢。我认为这可以通过将MAX(total &amp; 1) + ... 替换为SUM(distinct BitMask) 等来进一步缩短,就像我在OUTER APPLY 中所做的那样。
  • 那行不通——如果两个项目有两次相同的位,它将被添加而不是 ored(ored 一个词?)——它适用于你,因为你在递归上并不懒惰像我一样 - 有额外的项目或上面的项目 - 所以在你的例子中,数据 id 2 最终会得到 8 的结果(将添加来自父级的 1 位中的两个。)
  • 您必须使用像 _Bits_ 这样的 CTE,并将其加入到 extrarows,就像我在 OUTER APPLY 中加入到 BitValues 一样。这实际上是位或设置的唯一位列表。
  • 如果我理解正确:位是否重复并不重要,因为任何一位的位或运算 0..N 都会产生相同的结果。
  • 我的查询正确,sum(distinct bitmask) 失败。即使在这个小测试用例中,您也可以看到这一点。如果您将选择替换为select * from extrarows,这应该很清楚。对于 id 2,它将添加 3 和 5 得到 8。
【解决方案2】:

对霍根的回答稍作改进 (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

【讨论】:

  • 我很想知道哪个更快,这更少打字,但工作应该是相同的inner join _Bits_ y on (a.total &amp; y.BitMask) &lt;&gt; 0应该与丑陋的MAX()调用相同的操作数我的代码。请让我们(我)知道是否有更好的表现。
  • 您的版本快了大约 60%,但我们谈论的是给定测试数据的 8 毫秒差异。
  • 我认为费用不会随着数据集而扩大。构建_Bits_ CTE 似乎会受到一次性打击。
  • 我有一个想法如何组合这两个版本,但我今晚回家之前无法测试它。
猜你喜欢
  • 1970-01-01
  • 2013-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-30
  • 1970-01-01
相关资源
最近更新 更多