SQL Server - 使用按位运算符查找最高优先级项

时间:2010-02-27 13:20:58

标签: sql-server performance tsql stored-procedures bit-manipulation

问题:

我想根据Exception表中当前分配的优先级,返回主数据表中的所有行以及最高优先级异常。

我在下面创建了一个简化的数据设置示例(使用创建脚本),希望您可以帮助解决相当快速的T-SQL问题。

设置:

我有一个主数据表,其中每行可以有一个或多个异常存储为位掩码。

 CREATE TABLE [dbo].[PrimaryData](
    Id [INT] IDENTITY(1,1) NOT NULL,
    SomeData [VARCHAR](30) NOT NULL,
    Exceptions [INT] NOT NULL,
 )
 INSERT INTO [dbo].[PrimaryData](SomeData, Exceptions)
    VALUES('Data A', 0)
 INSERT INTO [dbo].[PrimaryData](SomeData, Exceptions)
    VALUES('Data B', 6)
 INSERT INTO [dbo].[PrimaryData](SomeData, Exceptions)
    VALUES('Data C', 6)
 INSERT INTO [dbo].[PrimaryData](SomeData, Exceptions)
    VALUES('Data D', 192)
 INSERT INTO [dbo].[PrimaryData](SomeData, Exceptions)
    VALUES('Data E', 132)

异常存储在查找表中纯粹是因为每个异常都被赋予了用户指定的优先级。此表不能由最终用户添加或删除行,他们只能控制每个例外的优先级, 1是最高

CREATE TABLE [dbo].[Exception](
    Id [INT] IDENTITY(1,1) NOT NULL,
    Priority [INT] NOT NULL, 
    Mask [SMALLINT] NOT NULL,
    Description [VARCHAR](30) NOT NULL
 )
 INSERT INTO [dbo].[Exception] (Priority, Mask, Description)
      VALUES(1, 1, 'Exception A')
 INSERT INTO [dbo].[Exception] (Priority, Mask, Description)
      VALUES(2, 2, 'Exception B')
 INSERT INTO [dbo].[Exception] (Priority, Mask, Description)
      VALUES(3, 4, 'Exception C')
 INSERT INTO [dbo].[Exception] (Priority, Mask, Description)
      VALUES(4, 8, 'Exception D')
 INSERT INTO [dbo].[Exception] (Priority, Mask, Description)
      VALUES(5, 16, 'Exception E')
 INSERT INTO [dbo].[Exception] (Priority, Mask, Description)
      VALUES(6, 32, 'Exception F')
 INSERT INTO [dbo].[Exception] (Priority, Mask, Description)
      VALUES(7, 64, 'Exception G')
 INSERT INTO [dbo].[Exception] (Priority, Mask, Description)
      VALUES(8, 128, 'Exception H')
 INSERT INTO [dbo].[Exception] (Priority, Mask, Description)
      VALUES(9, 256, 'Exception I')

因此,基于提供的示例数据,我想返回SomeData,Mask(具有最高优先级)和Description(具有最高优先级)。

即。

| Data B | 2 | Exception B

显然,我需要以最有效的方式执行此操作,因为主数据表中可能会返回25K行。

提前致谢。

4 个答案:

答案 0 :(得分:2)

SELECT  *
FROM    PrimaryData pd
CROSS APPLY
        (
        SELECT  TOP 1 *
        FROM    Exception e
        WHERE   e.Mask & pd.Exceptions <> 0
        ORDER BY
                e.Priority
        ) q

答案 1 :(得分:1)

这将获得您想要的单个PrimaryData行。

select top 1 SomeData, Mask
  from PrimaryData
    inner join Exceptions
      on (PrimaryData.Exceptions & Exceptions.Mask <> 0)
  where PrimaryData.Id = 27
  order by Priority

对于所有行,这样的东西应该有效(按照Quassnoi的建议编辑)

with data as (
  select SomeData, Mask, row_number() over
      (partition by PrimaryData.Id order by Priority) AS row
    from PrimaryData
      inner join Exceptions
        on (PrimaryData.Exceptions & Exceptions.Mask <> 0)
)
select * 
  from data
  where row = 1

编辑改变|到&amp;

答案 2 :(得分:0)

首先创建一个检测异常掩码的(确定性)函数。这个位于最低位:

CREATE FUNCTION GetPriorityMask(@value int)
RETURNS smallint
with schemabinding
AS
BEGIN
    declare @mask smallint

    if @value = 0 return null

    set @mask = 1
    while @mask <= @value
    begin
        if @value | @mask = @value 
            break;

        set @mask = @mask * 2
    end

    RETURN @mask

END

然后将一个持久计算列添加到PrimaryDataTable,该值是函数的结果。

alter table PrimaryData
add PriorityMask as (dbo.GetPriorityMask(Exceptions)) persisted

现在你只需要为列添加一个索引

create index IX_PrimaryDate_PriorityMask
on PrimaryData(PriorityMask)

当然,添加外键也不是一个坏主意,但首先应该在异常表中添加一个唯一的密钥:

alter table Exception
add constraint UQ_Exception_Mask UNIQUE (Mask)

现在添加外键

alter table PrimaryData
add constraint FK_PrimaryData_Exception foreign key(PriorityMask) references Exception(Mask)

现在检索您的数据:

select *
from PrimaryData
left join Exception on PrimaryData.PriorityMask = Exception.Mask

答案 3 :(得分:0)

我的答案的灵感来自“查找整数对数基数10的整数,显而易见的方法”技巧here

请注意,如果您要安排优先级以使最高位对应于最高优先级异常,则可以直接使用该技巧,适用于基数2.或者,您可以加入简单公式FLOOR(LOG(Exceptions)/LOG(2) 。 (在这两种解决方案中,您需要允许掩码的最高位使用或特殊情况,因为它会使整数为负。)

SELECT SomeData, Mask, Description
FROM PrimaryData
INNER JOIN Exception ON
    CASE
        WHEN Exceptions & 0x1 <> 0 THEN 1
        WHEN Exceptions & 0x2 <> 0 THEN 2
        WHEN Exceptions & 0x4 <> 0 THEN 4
        WHEN Exceptions & 0x8 <> 0 THEN 8
        WHEN Exceptions & 0x10 <> 0 THEN 16
        WHEN Exceptions & 0x20 <> 0 THEN 32
        WHEN Exceptions & 0x40 <> 0 THEN 64
        WHEN Exceptions & 0x80 <> 0 THEN 128
        WHEN Exceptions & 0x100 <> 0 THEN 256
        WHEN Exceptions & 0x200 <> 0 THEN 512
        WHEN Exceptions & 0x400 <> 0 THEN 1024
        WHEN Exceptions & 0x800 <> 0 THEN 2048
        WHEN Exceptions & 0x1000 <> 0 THEN 4096
        WHEN Exceptions & 0x2000 <> 0 THEN 8192
        WHEN Exceptions & 0x4000 <> 0 THEN 16384
        WHEN Exceptions & 0x8000 <> 0 THEN 32768
        WHEN Exceptions & 0x10000 <> 0 THEN 65536
        WHEN Exceptions & 0x20000 <> 0 THEN 131072
        WHEN Exceptions & 0x40000 <> 0 THEN 262144
        WHEN Exceptions & 0x80000 <> 0 THEN 524288
        WHEN Exceptions & 0x100000 <> 0 THEN 1048576
        WHEN Exceptions & 0x200000 <> 0 THEN 2097152
        WHEN Exceptions & 0x400000 <> 0 THEN 4194304
        WHEN Exceptions & 0x800000 <> 0 THEN 8388608
        WHEN Exceptions & 0x1000000 <> 0 THEN 16777216
        WHEN Exceptions & 0x2000000 <> 0 THEN 33554432
        WHEN Exceptions & 0x4000000 <> 0 THEN 67108864
        WHEN Exceptions & 0x8000000 <> 0 THEN 134217728
        WHEN Exceptions & 0x10000000 <> 0 THEN 268435456
        WHEN Exceptions & 0x20000000 <> 0 THEN 536870912
        WHEN Exceptions & 0x40000000 <> 0 THEN 1073741824
        WHEN Exceptions & 0x80000000 <> 0 THEN -2147483648
        ELSE 0
    END = Mask
WHERE Exceptions <> 0
ORDER BY PrimaryData.Id