2010-10-20 5 views
21

der folgenden Tabelle angegeben:Aggregate bitweise OR in einer Unterabfrage

CREATE TABLE BitValues (n int) 

Ist es möglich, die bitweise ODER-Verknüpfung von n für alle Zeilen innerhalb einer Unterabfrage zu berechnen? Zum Beispiel, wenn Bitwerte enthält diese 4 Zeilen:

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

würde ich die Unterabfrage erwarten 7. zurückzukehren Gibt es eine Möglichkeit, diese Inline zu tun, ohne UDF zu schaffen?

Antwort

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

Der Ausführungsplan sieht ein bisschen besser aus als der von Andomars Lösung. Vielleicht kann jemand, der Ausführungspläne besser entschlüsseln kann, wiegen. – Daniel

+0

Wenn ich diese und @ Andomars Lösung in der gleichen Charge lief, waren es 44% der Charge. – Daniel

+1

+1 Obwohl dieses nur 4 Bits unterstützt, ist es schneller, weil es einen 'linken Semi-Join' ohne eine 'eindeutige Sortierung' macht. Bearbeitete meine Abfrage, um das Gleiche zu tun. Cool. :) – Andomar

2

können Sie eine Variable verwenden, und machen Sie einen "bitweise oder" (|) für jede Zeile:

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 

Dieser druckt 7. Beachten Sie, dass die Zuweisung von Variablen in einer Auswahl nicht offiziell unterstützt wird.

In strenger SQL-Weise können Sie eine Tabelle mit einer Zeile für jedes Bit erstellen. Diese Tabelle hätte 31 Zeilen, da das 32. Bit eine negative Ganzzahl ist. In diesem Beispiel wird eine rekursive CTE diese Tabelle zu erstellen:

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 
     ) 

Dies summiert die Bits, wobei jedes Bit im Quelltabelle gesetzt.

+1

Dies ist nicht inline, innerhalb einer Unterabfrage. Ich möchte, dass das Ergebnis von einer äußeren Abfrage verwendet werden kann. – Daniel

+0

@Daniel: Sie können dies in benutzerdefinierten Funktion (UDF) und verwenden Sie es aus einer äußeren Abfrage – Andomar

+1

Wie Sie vermeiden, eine UDF verwenden ist Teil der Frage. – Daniel

0

Sind Sie auf der Suche nach so etwas?

EDIT: Wie bereits in anderen Kommentaren wurde diese Antwort basiert auf der Annahme, dass die Bitwerte Tabelle nur Potenzen von 2 enthalten würde ich zwischen den Zeilen der Frage und schließt eine Verwendung für die Inline-Unterabfrage zu lesen versucht, .

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

Nicht ganz, aber Sie haben mir eine Idee. Ich brauche alle Werte OR'd zusammen, nicht nur ein paar Bits überprüfen. – Daniel

-1

Ihre beste Wette für eine lesbare und wiederverwendbare Lösung wäre ein ein benutzerdefiniertes CLR Aggregate schreiben bit- oder auszuführen. http://msdn.microsoft.com/en-us/library/91e6taax(VS.80).aspx

+0

Es ist ein Kinderspiel in T-SQL als UDF zu schreiben. Ich wollte nur vermeiden, eine Single-Use-Funktion zu schreiben, und dachte, es wäre eine interessante Herausforderung. – Daniel

6

Eine einfache Lösung, die eine Mischung ist von @ AlexKuznetsov der und @ Andomar Lösungen: Ein Tutorial für diese Art der Operation zu schaffen ist hier zu finden.
Die Bitmaske wird durch einen rekursiven allgemeinen Tabellenausdruck erzeugt, jedoch auf eine einfachere Weise als in @ Andomars Lösung.
Die Bits werden dann wie in @ AlexKuznetsov Lösung summiert.
In diesem Beispiel nehme ich an, dass eine 16-Bit-Maske erforderlich ist, daher die Grenze von 65536. Sie können eine N-Bit-Maske angeben, indem Sie 65536 in 2^N ändern.

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

Sehr schöne Lösung in der Tat. – mzedeler

1

Ich versuchte COALESCE-Funktion und es funktioniert, Beispiel:

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

Ich bin mir dieser Lösung bewusst, aber die Frage gibt explizit _within eine Unterabfrage_ an. – Daniel

3

Vorbereitungen:

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

Lösung:

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

Ich mag dies trotz seiner Einschränkung, dass Sie die maximale Größe der Bitmaske angeben müssen, die Sie unterstützen möchten. – Brad

+0

edit (derp): habe gerade gemerkt, dass die akzeptierte Antwort auch so ist, tauscht sie 'max (n & [bitvalue])' für den linken Join auf den Bitvalue-Cte aus. – Brad

1

Dies ist eine Alternative, ohne (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; 

auch eine Gruppe von Beispiel betrachten:

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

Das erste Beispiel ist gemeint, als bei langsamer sein. Wenn Sie jedoch GroupBy mit anderen Daten verwenden, sind die Abfragen weitgehend kostenmäßig identisch.

Ein anderer Weg, dies zu tun ist

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 ist wegen der Wiederholung in Code ein bisschen schlimmer, ein bisschen besser lesbar und ähnlich in Ausführungskosten.

2

ich diesen Beitrag sehen, ist ziemlich alt und es gibt einige nützliche Antworten, aber das ist ein ziemlich verrückt straight forward-Methode ...

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

MAX() anstelle von SUM (distinct()) für jede bitweise Operation wäre wahrscheinlich effizienter (ungetestet) und ergibt das gleiche Ergebnis. MIN bei jeder bitweisen Operation wäre dann wie ein aggregiertes bitweises UND. Dies ist jedoch immer noch die einfachste (und daher beste) Methode. – Arkaine55

+0

Wow! Ein tatsächlicher Anwendungsfall für 'sum (distinct)'. –

0

für mich, dass die beste Lösung ist.

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