2010-10-20 10 views
21

Dada la siguiente tabla:bit a bit-o agregado en una subconsulta

CREATE TABLE BitValues (n int) 

¿Es posible calcular el bit a bit-OR de n para todas las filas dentro de una subconsulta? Por ejemplo, si BitValues ​​contiene estos 4 filas:

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

Yo esperaría que la subconsulta para volver 7. ¿Hay una manera de hacer esto en línea, sin crear una UDF?

Respuesta

11
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 
+0

El plan de ejecución de este se ve un poquito mejor que el de la solución de @ Andomar. Tal vez alguien que pueda descifrar mejor los planes de ejecución pueda pesar. – Daniel

+0

Cuando ejecuté esto y la solución de @ Andomar en el mismo lote, fue el 44% del lote. – Daniel

+1

+1 Aunque este solo admite 4 bits, es más rápido porque hace una 'semi unión izquierda 'sin un' tipo distinto'. Edité mi consulta para hacer lo mismo. Guay. :) – Andomar

2

Se puede utilizar una variable y hacer un "bit a bit o" (|) para cada fila:

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 

Esto imprime 7. Tenga en cuenta que la asignación de variables en una selección no es oficialmente compatible.

De una manera más estrictamente SQL, puede crear una tabla con una fila para cada bit. Esta tabla tendría 31 filas, ya que el 32º bit es un entero negativo. Este ejemplo utiliza un CTE recursiva para crear dicho cuadro:

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 
     ) 

Esto resume los bits donde se establece cualquier bit en la tabla de origen.

+1

Esto no está en línea, dentro de una subconsulta. Me gustaría que el resultado se pueda utilizar a partir de una consulta externa. – Daniel

+0

@Daniel: Puede colocar esto en la función definida por el usuario (UDF) y usarlo desde una consulta externa – Andomar

+1

Cómo evitar el uso de un UDF es parte de la pregunta. – Daniel

0

¿Estás buscando algo como esto?

EDITAR: Como se ha señalado en otros comentarios, esta respuesta se basa en la suposición de que la mesa BitValues ​​contendría solamente potencias de 2. Traté de leer entre las líneas de la pregunta e inferir un uso para la sub consulta en línea .

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

No del todo, pero me diste una idea. Necesito todos los valores O juntos, no solo controlo algunos bits. – Daniel

-1

Su mejor apuesta para una solución fácil de leer y reutilizable sería escribir un una costumbre CLR agregado para realizar bit a bit o. Un tutorial para crear este tipo de operación se puede encontrar aquí: http://msdn.microsoft.com/en-us/library/91e6taax(VS.80).aspx

+0

Escribir un texto en T-SQL como UDF es un pedazo de pastel. Solo estaba tratando de evitar escribir una función de un solo uso, y pensé que parecía un desafío interesante. – Daniel

6

Una solución sencilla que es una mezcla de AlexKuznetsov @ de @ y soluciones de Andomar.
La máscara de bits se genera mediante una Expresión común de tabla recursiva, pero de una manera más simple que en la solución de @ Andomar.
Los bits se suman al igual que en la solución de @ AlexKuznetsov.
En este ejemplo, supongo que se requiere una máscara de 16 bits, de ahí el límite 65536. Puede indicar una máscara de N bits cambiando 65536 a 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 
+0

Muy buena solución de hecho. – mzedeler

1

He intentado utilizar la función COALESCE y funciona, por ejemplo:

DECLARE @nOrTotal INT 

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

SELECT @nOrTotal 

>> Result: 3 
+1

Conozco esta solución, pero la pregunta establece explícitamente _within subquery_. – Daniel

3

Preparativos:

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

Solución:

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

me gusta a pesar de su limitación de que debe especificar el tamaño máximo de máscara de bits que desea admitir. – Brad

+0

edit (derp): me acabo de dar cuenta de que la respuesta aceptada también lo hace, esto simplemente intercambia 'max (n & [bitvalue])' para la combinación a la izquierda del bitvalue cte. – Brad

1

Esta es una alternativa, sin WITH (¡¡¡Hurra!!!):

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; 

Ten en cuenta también un Grupo Por ejemplo:

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

El primer ejemplo está destinado a ser más lenta que con. Sin embargo, cuando usa GroupBy con algunos otros datos, las consultas son en gran medida las mismas en cuanto a costos.

Otra manera de hacer esto es

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; 

Es un poco peor debido a la repetición de código, un poco más fácil de leer y similar en coste de ejecución.

2

veo este post es bastante viejo y hay algunas respuestas útiles, pero este es un método directo y bastante loco ...

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

MAX() en lugar de SUM (distinct()) para cada operación bit a bit probablemente sea más eficiente (no probado) y dé el mismo resultado. MIN en cada operación bit a bit sería como hacer un agregado en modo bit. Y. Sin embargo, este sigue siendo el método más simple (y por lo tanto el mejor). – Arkaine55

+0

¡Guau! Un caso de uso real para 'sum (distinct)'. –

0

Para mí esa es la mejor solución.

declare @res int 
set @res=0  
SELECT @[email protected]|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