【问题标题】:Aggregate bitwise-OR in a subquery在子查询中聚合按位或
【发布时间】:2011-04-28 05:16:03
【问题描述】:

给定下表:

CREATE TABLE BitValues ( n int )

是否可以为子查询中的所有行计算n 的按位或?例如,如果 BitValues 包含这 4 行:

+---+ | n | +---+ | 1 | | 2 | | 4 | | 3 | +---+

我希望子查询返回 7。有没有办法以内联方式执行此操作,无需创建 UDF

【问题讨论】:

    标签: sql-server sql-server-2005 tsql aggregation


    【解决方案1】:
    WITH    Bits
              AS ( SELECT   1 AS BitMask
                   UNION ALL
                   SELECT   2
                   UNION ALL
                   SELECT   4
                   UNION ALL
                   SELECT   8
                   UNION ALL
                   SELECT   16
                 )
        SELECT  SUM(DISTINCT BitMask)
        FROM    ( SELECT    1 AS n
                  UNION ALL
                  SELECT    2
                  UNION ALL
                  SELECT    3
                  UNION ALL
                  SELECT    4
                  UNION ALL
                  SELECT    5
                  UNION ALL
                  SELECT    6
                ) AS t
                JOIN Bits ON t.n & Bits.BitMask > 0
    

    【讨论】:

    • 这个执行计划看起来比@Andomar 的解决方案好一点。也许能够更好地解读执行计划的人可以参与进来。
    • 当我在同一批次中运行这个和@Andomar 的解决方案时,它是批次的 44%。
    • +1 虽然这个只支持 4 位,但它更快,因为它在没有 distinct sort 的情况下执行 left semi join。编辑我的查询以执行相同的操作。凉爽的。 :)
    • @Andomar - 我最终混合了您的查询和他的查询。我赞成你的。感谢您的帮助。
    • 我不知道如何使用它。我尝试将值 (1,2,4,3) 添加到顶部的 CTE。它返回 10 而不是 7。
    【解决方案2】:

    我看到这篇文章很老了,有一些有用的答案,但这是一个非常疯狂的直接方法......

    Select  
        SUM(DISTINCT(n & 0x01)) +
        SUM(DISTINCT(n & 0x02)) +
        SUM(DISTINCT(n & 0x04))
        as OrN
    From BitValues
    

    【讨论】:

    • MAX() 而不是 SUM(distinct()) 对于每个按位运算可能会更有效(未经测试)并给出相同的结果。然后,每个按位运算的 MIN 就像进行聚合按位与。不过,这仍然是最简单(因此也是最好)的方法。
    • 哇! sum(distinct) 的实际用例。
    【解决方案3】:

    一个简单的解决方案,它混合了@AlexKuznetsov 和@Andomar 的解决方案。
    位掩码由递归公用表表达式生成,但比@Andomar 的解决方案更简单。
    然后就像在@AlexKuznetsov 的解决方案中一样对这些位求和。
    在此示例中,我假设需要 16 位掩码,因此限制为 65536。您可以通过将 65536 更改为 2^N 来指示 N 位掩码。

    WITH Bits AS
    (
        SELECT 1 BitMask
        UNION ALL
        SELECT 2 * BitMask FROM Bits WHERE BitMask < 65536 -- recursion
    )
    SELECT SUM(DISTINCT BitMask)
    FROM
        (SELECT 1 n
        UNION ALL
        SELECT 2 n
        UNION ALL
        SELECT 4 n
        UNION ALL
        SELECT 3 n) t
        INNER JOIN Bits ON t.n & Bits.BitMask > 0
    

    【讨论】:

    • 确实非常好的解决方案。
    【解决方案4】:

    准备工作:

    if object_id(N'tempdb..#t', N'U') is not null drop table #t;
    create table #t ( n int );
    insert into #t values (1), (2), (4), (3);
    

    解决方案:

    select max(n & 8) + max(n & 4) + max(n & 2) + max(n & 1) from #t;
    

    【讨论】:

    • 我喜欢这个,尽管它有限制,你必须指定你想要支持的最大位掩码大小。
    • edit (derp):刚刚意识到接受的答案也是如此,这只是将 max(n &amp; [bitvalue]) 交换为位值 cte 的左连接。
    【解决方案5】:

    您可以使用变量并对每一行执行“按位或”(|):

    declare @t table (n int)
    insert @t select 1 union select 2 union select 4
    
    declare @i int
    set @i = 0
    
    select  @i = @i | n
    from    @t
    
    select @i
    

    这将打印7。请注意,官方不支持在选择中分配变量。

    在更严格的 SQL 方式中,您可以创建一个表,其中一位为一行。该表将有 31 行,因为第 32 位是负整数。此示例使用递归 CTE 创建该表:

    declare @t table (n int)
    insert @t select 1 union select 2 union select 3
    
    ; with bits(nr, pow) as 
    (
        select  1
        ,       1
        union all
        select  nr + 1
        ,       pow * 2
        from    bits
        where   nr <= 30
    )
    select  sum(b.pow)
    from    bits b
    where   exists
            (
            select  *
            from    @t t  
            where   b.pow & t.n > 0
            )
    

    这将源表中设置任何位的位相加。

    【讨论】:

    • 这不是内联的,在子查询中。我希望结果可以从外部查询中使用。
    • @Daniel:您可以将其放在用户定义函数 (UDF) 中并从外部查询中使用它
    • 如何避免使用 UDF 是问题的一部分。
    • @Daniel:您可以将第二个查询与 CTE 一起使用。它会起作用,但会很慢。 (考虑使用 CLR 聚合来提高性能。)
    • @Andomar:我一直在思考这些问题,但和你一样,我认为使用 CROSS JOIN 来“爆炸”结果集可能会变慢。
    【解决方案6】:

    我尝试使用 COALESCE 函数并且它有效,例如:

    DECLARE @nOrTotal INT
    
    SELECT @nOrTotal = COALESCE(@nOrTotal, 0) | nValor 
        FROM (SELECT 1 nValor
                  UNION 
              SELECT 2
                  UNION 
              SELECT 2) t
    
    SELECT @nOrTotal
    
    >> Result: 3
    

    【讨论】:

    • 我知道这个解决方案,但问题明确指出在子查询中
    【解决方案7】:

    这是一个替代方案,没有 WITH(万岁!!!):

        select sum(distinct isnull(n & BitMask, 0)) as resultvalue
        from 
        (
              SELECT    1 AS n
              UNION ALL
              SELECT    2
              UNION ALL
              SELECT    4
              UNION ALL
              SELECT    3
        ) t
        INNER JOIN (SELECT 0 BitMask union all SELECT 1 union all SELECT 2 union all SELECT 4 union all SELECT 8 union all SELECT 16 union all SELECT 32 union all SELECT 64 union all SELECT 128 union all SELECT 256 union all SELECT 512 union all SELECT 1024 union all SELECT 2048 union all SELECT 4096 union all SELECT 8192 union all SELECT 16384 union all SELECT 32768 union all SELECT 65536) Bits -- = SELECT POWER(2, 16)
        ON n & BitMask = BitMask;
    

    还考虑一个 Group By 示例:

     -- Setup temp table to produce an example --
     create table #BitValues
     (
        id int identity(1,1)
        ,value int
        ,groupby varchar(10)
     )
    
     insert into #BitValues
     SELECT    1 AS value, 'apples'
              UNION ALL
              SELECT    2, 'apples'
              UNION ALL
              SELECT    4, 'apples'
              UNION ALL
              SELECT    3, 'apples'
    
     -- Bit operation: --
      select groupby, sum(distinct isnull(value & BitMask, 0)) as tempvalue
      from #BitValues
      INNER JOIN (SELECT 0 BitMask union all SELECT 1 union all SELECT 2 union all SELECT 4 union all SELECT 8 union all SELECT 16 union all SELECT 32 union all SELECT 64 union all SELECT 128 union all SELECT 256 union all SELECT 512 union all SELECT 1024 union all SELECT 2048 union all SELECT 4096 union all SELECT 8192 union all SELECT 16384 union all SELECT 32768 union all SELECT 65536) Bits -- = SELECT POWER(2, 16)
          ON value & BitMask = BitMask
      group by groupby
    

    第一个例子的意思是比 WITH 慢。但是,当您将 GroupBy 与其他一些数据一起使用时,查询在成本方面基本相同。

    另一种方法是

        select 
        groupby
          ,max(case when n & 1 = 1 then 1 else 0 end)
                +
            max(case when n  & 2 = 2 then 2 else 0 end)
                +
            max(case when n & 4 = 4 then 4 else 0 end)  
                +
            max(case when n & 8 = 8 then 8 else 0 end)
                +
            max(case when n & 16 = 16 then 16 else 0 end)
                +
            max(case when n & 32 = 32 then 32 else 0 end)
                +
            max(case when n & 64 = 64 then 64 else 0 end)
                +
            max(case when n & 128 = 128 then 128 else 0 end)
                +
            max(case when n & 256 = 256 then 256 else 0 end)
                +
            max(case when n & 512 = 512 then 512 else 0 end)
                +
            max(case when n & 1024 = 1024 then 1024 else 0 end)
                as NewDNC
        from #BitValues
        group by groupby;
    

    由于代码重复,它有点糟糕,可读性更高,执行成本也差不多。

    【讨论】:

      【解决方案8】:

      你在寻找这样的东西吗?

      编辑:正如其他 cmets 所述,此答案基于 BitValues 表仅包含 2 的幂的假设。我试图在问题的两行之间阅读并推断用途用于内联子查询。

      declare @BitValues table (
          n int
      )
      
      declare @TestTable table (
          id int identity,
          name char(10),
          BitMappedColumn int
      )
      
      insert into @BitValues (n)
          select 1 union all select 2 union all select 4
      
      insert into @TestTable
          (name, BitMappedColumn)
          select 'Joe', 5 union all select 'Bob', 8
      
      select t.id, t.name, t.BitMappedColumn
          from @TestTable t
              inner join (select SUM(n) as BitMask from @BitValues) b
                  on t.BitMappedColumn & b.BitMask <> 0
      

      【讨论】:

      • 不完全是,但你给了我一个想法。我需要所有的值 OR'd 在一起,而不仅仅是检查一些位。
      【解决方案9】:

      对我来说这是最好的解决方案。

      declare @res int
      set @res=0    
      SELECT  @res=@res|t.n
          FROM    ( SELECT    1 AS n
                    UNION ALL
                    SELECT    2
                    UNION ALL
                    SELECT    3
                    UNION ALL
                    SELECT    4
                    UNION ALL
                    SELECT    5
                    UNION ALL
                    SELECT    6
                  ) AS t
      

      【讨论】:

        【解决方案10】:

        对于可读和可重用的解决方案,最好的办法是编写一个自定义 CLR 聚合来执行按位或。创建此类操作的教程可以在这里找到:http://msdn.microsoft.com/en-us/library/91e6taax(VS.80).aspx

        【讨论】:

        • 在 T-SQL 中将其编写为 UDF 简直是小菜一碟。我只是想避免编写一次性函数,并认为这似乎是一个有趣的挑战。
        猜你喜欢
        • 2021-08-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-04
        • 2016-06-28
        • 1970-01-01
        • 2019-06-13
        • 1970-01-01
        相关资源
        最近更新 更多