开发者

Aggregate bitwise-OR in a subquery

Given the following table:

CREATE TABLE BitValues ( n int )

Is 开发者_StackOverflowit possible to compute the bitwise-OR of n for all rows within a subquery? For example, if BitValues contains these 4 rows:

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

I would expect the subquery to return 7. Is there a way to do this inline, without creating a UDF?


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


I see this post is pretty old and there are some useful answers but this is a pretty crazy straight forward method...

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


A simple solution which is a mix of @AlexKuznetsov's and @Andomar's solutions.
The bit mask is generated by a recursive Common Table Expression, but in a simpler way than in @Andomar's solution.
The bits are then summed just like in @AlexKuznetsov's solution.
In this example I assume a 16 bits mask is required, hence the 65536 limit. You can indicate a N-bits mask by changing 65536 to 2^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


Preparations:

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);

Solution:

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


You can use a variable and do a "bitwise or" (|) for each row:

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

This prints 7. Note that assigning variables in a select is not officially supported.

In a more strictly SQL way, you can create a table with one row for each bit. This table would have 31 rows, as the 32nd bit is a negative integer. This example uses a recursive CTE to create that table:

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
        )

This sums the bits where any bit in the source table is set.


I tried using COALESCE function and it works, example:

DECLARE @nOrTotal INT

SELECT @nOrTotal = COALESCE(@nOrTotal, 0) | nValor 
    FROM (SELECT 1 nValor
              UNION 
          SELECT 2
              UNION 
          SELECT 2) t

SELECT @nOrTotal

>> Result: 3


This is an alternative, without WITH (hurrah!!!):

    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;

Also consider a Group By example:

 -- 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

The first example is meant to be slower than WITH. However when you use GroupBy with some other data, the queries are largely the same cost-wise.

Another way to do this is

    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;

It's a bit worse because of repetition in code, a bit more readable and similar in execution cost.


Are you looking for something like this?

EDIT: As noted in other comments, this answer was based on the assumption that the BitValues table would only contain powers of 2. I tried to read between the lines of the question and infer a use for the inline subquery.

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


For me that is the best solution.

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


Your best bet for a readable and re-usable solution would be to write a a custom CLR Aggregate to perform bitwise or. A tutorial for creating this type of operation can be found here: http://msdn.microsoft.com/en-us/library/91e6taax(VS.80).aspx

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜