2010-10-20 36 views
21

Với bảng sau:tổng hợp Bitwise-OR trong một subquery

CREATE TABLE BitValues (n int) 

Có thể tính toán bitwise-OR của n cho tất cả các hàng trong một subquery? Ví dụ, nếu BitValues ​​chứa những 4 hàng:

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

tôi mong chờ các subquery để trở 7. Có cách nào để làm nội tuyến này, mà không cần tạo một UDF?

Trả lời

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

Kế hoạch thực hiện điều này có vẻ tốt hơn một chút so với giải pháp của @ Andomar. Có lẽ ai đó có thể giải mã tốt hơn kế hoạch thực hiện có thể cân nhắc. – Daniel

+0

Khi tôi chạy giải pháp này và @ Andomar trong cùng một lô, đó là 44% của lô. – Daniel

+1

+1 Mặc dù cái này chỉ hỗ trợ 4 bit, nhưng nó nhanh hơn vì nó thực hiện một 'phép nối bán trái' mà không có một' phân loại riêng biệt'. Đã chỉnh sửa truy vấn của tôi để thực hiện tương tự. Mát mẻ. :) – Andomar

2

Bạn có thể sử dụng một biến và làm một "Bitwise hoặc" (|) cho mỗi hàng:

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 

này in 7. Lưu ý rằng việc gán các biến trong một lựa chọn không được hỗ trợ chính thức.

Theo cách SQL đúng hơn, bạn có thể tạo bảng có một hàng cho mỗi bit. Bảng này sẽ có 31 hàng, vì bit 32 là một số nguyên âm. Ví dụ này sử dụng CTE đệ quy để tạo bảng đó:

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 
     ) 

Điều này tính tổng bit mà bất kỳ bit nào trong bảng nguồn được đặt.

+1

Đây không phải là nội tuyến, trong truy vấn phụ. Tôi muốn kết quả có thể sử dụng được từ truy vấn bên ngoài. – Daniel

+0

@Daniel: Bạn có thể đặt điều này trong hàm do người dùng định nghĩa (UDF) và sử dụng nó từ truy vấn bên ngoài – Andomar

+1

Cách tránh sử dụng UDF là một phần của câu hỏi. – Daniel

0

Bạn đang tìm kiếm một cái gì đó như thế này?

EDIT: Như đã lưu ý trong các nhận xét khác, câu trả lời này dựa trên giả định rằng bảng BitValues ​​chỉ chứa quyền hạn của 2. Tôi đã cố gắng đọc giữa các dòng câu hỏi và suy ra sử dụng cho truy vấn con nội tuyến .

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

Không hoàn toàn, nhưng bạn đã cho tôi một ý tưởng. Tôi cần tất cả các giá trị OR cùng nhau, không chỉ kiểm tra một số bit. – Daniel

-1

Đặt cược tốt nhất cho giải pháp có thể đọc và có thể sử dụng lại của bạn là viết CLR tùy chỉnh để thực hiện bitwise hoặc. Một hướng dẫn để tạo kiểu này hoạt động có thể được tìm thấy ở đây: http://msdn.microsoft.com/en-us/library/91e6taax(VS.80).aspx

+0

Đó là một mẩu bánh để viết điều này trong T-SQL dưới dạng UDF. Tôi chỉ cố gắng tránh viết một hàm sử dụng một lần, và nghĩ nó có vẻ như là một thử thách thú vị. – Daniel

6

Một giải pháp đơn giản mà là một sự pha trộn của @ AlexKuznetsov và @ giải pháp Andomar của.
Mặt nạ bit được tạo bởi Biểu thức bảng chung đệ quy, nhưng theo cách đơn giản hơn so với giải pháp @ Andomar.
Các bit sau đó được tổng hợp giống như trong giải pháp của @ AlexKuznetsov.
Trong ví dụ này, tôi giả định một mặt nạ 16 bit là bắt buộc, do đó giới hạn 65536. Bạn có thể chỉ ra mặt nạ N-bit bằng cách thay đổi 65536 thành 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

Giải pháp rất tốt đẹp thực sự. – mzedeler

1

tôi đã cố gắng sử dụng liên hiệp chức năng và nó hoạt động, ví dụ:

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

Tôi biết về giải pháp này, nhưng câu hỏi nêu rõ _within subquery_. – Daniel

3

chế:

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

Giải pháp:

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

Tôi thích điều này mặc dù giới hạn của nó là bạn phải chỉ định kích thước tối đa của bitmask bạn muốn hỗ trợ. – Brad

+0

chỉnh sửa (derp): chỉ nhận ra rằng câu trả lời được chấp nhận cũng có, điều này chỉ hoán đổi 'max (n & [bitvalue])' cho phép nối trái với bitteue cte. – Brad

1

Đây là một sự thay thế, mà không VỚI (tiếng hoan hô!!!):

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; 

Cũng xem xét một Nhóm By dụ:

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

Ví dụ đầu tiên được hiểu là chậm hơn so với VỚI. Tuy nhiên, khi bạn sử dụng GroupBy với một số dữ liệu khác, các truy vấn chủ yếu là chi phí khôn ngoan.

Một cách khác để làm điều này là

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; 

Đó là một chút tồi tệ hơn vì sự lặp lại trong mã, nhiều hơn một chút có thể đọc được và tương tự ở chi phí thực hiện.

2

Tôi thấy bài này là khá cũ và có một số câu trả lời hữu ích nhưng đây là một phương pháp thẳng về phía trước khá điên ...

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

MAX() thay vì SUM (khác biệt()) cho mỗi thao tác bitwise có lẽ sẽ hiệu quả hơn (chưa kiểm tra) và đưa ra kết quả tương tự. MIN trên mọi hoạt động bitwise sau đó sẽ giống như thực hiện tổng hợp bitwise AND. Đây vẫn là phương pháp đơn giản nhất (và do đó tốt nhất). – Arkaine55

+0

Wow! Trường hợp sử dụng thực tế cho 'tổng (khác biệt)'. –

0

Đối với tôi đó là giải pháp tốt nhất.

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 
Các vấn đề liên quan