在Sql Server中按位AND

时间:2011-03-22 18:54:57

标签: sql-server bit-manipulation

我有一个非常典型的情况。我们有一个名为Users的表,它有一个名为Branches(varchar 1000)的列。

该组织可以拥有1000个分支机构。因此,如果用户有权访问分支1,5和10,则分支字符串将如下所示:

1000100001000000000 ...

(即1表示用户根据分支号码进行分支访问的位置)。请不要建议更好的数据存储选项,这是从遍布各大洲的遗留应用程序中找到的。

现在给出了这个背景(考虑到可能有> 10000个用户),我想搜索有权访问任何一组给定分支的所有用户,例如找到有权访问分支10,65,90或125的所有用户。

一个简单的解决方案是将所需的分支集(即10,65,90,125)转换为分支字符串(00000010100等),然后使用标量UDF迭代两个分支字符串并首先返回true匹配出现,其中2个分支字符串有1,如果在公共位置没有1则为假。

除此之外,我还可以选择在C#中搜索应用程序。其中一些用户具有特权(大约1000或更多),并且他们的数据在应用程序中缓存,因为它经常被访问。但对于没有特权的其他用户,数据仅在db。

我在这里有2个问题: 1)对于数据库搜索,除了我提到的UDF方法之外,还有更好的方法吗? 2)对于特权用户,在性能方面更好,在应用程序中搜索(进一步可以基于UDF中的分支字符串上的for循环,或者作为2个分支数组上的Linq Intersect运算符,即Linq Intersect [1,5,9,50,80,200]和[6,90,256,300]等) 数据库搜索会产生更快的结果还是基于应用程序的搜索?

考虑在这两种情况下可能还有其他搜索参数,例如:姓氏以。开头。

我目前的做法是首先在其他参数(如姓氏开头)中为这两种情况过滤db中的行。然后使用标量UDF根据分支过滤此结果集,然后返回结果。

6 个答案:

答案 0 :(得分:7)

在SQL中执行它,它只比在C#或其他前端中执行它快100倍。

使用内置数字表将长字符串分解为位置(数字系列最多可达2047)。

示例表

create table users (userid int)
insert users select 1 union all select 2

create table permission (userid int, bigstr varchar(1000))
insert permission
select 1, REPLICATE('0', 56) + '1' -- 57th
        + REPLICATE('0', 32) + '1' -- 90th
        + REPLICATE('0', 64) + '1' -- 155th
        + REPLICATE('0', 845)
insert permission
select 2, REPLICATE('0', 66) + '1' -- 67th
        + REPLICATE('0', 98) + '1' -- 166th
        + REPLICATE('0', 657) + '1' -- 824th
        + REPLICATE('0', 176)

显示针对列表的所有匹配权限的示例

select *
from users u
inner join permission p on p.userid=u.userid
inner join master..spt_values v on v.type='p'
  and SUBSTRING(p.bigstr,v.number,1) = '1'
  and v.number between 1 and LEN(p.bigstr)  -- or 1000 if it is always 1000
where v.number in (57,90,824)

查找有权访问列表中至少一个分支的用户:

select distinct u.userid
from users u
inner join permission p on p.userid=u.userid
inner join master..spt_values v on v.type='p'
  and SUBSTRING(p.bigstr,v.number,1) = '1'
  and v.number between 1 and LEN(p.bigstr)  -- or 1000 if it is always 1000
where v.number in (57,90,824)

等。

答案 1 :(得分:3)

您可能希望为LIKE查询构造__1_1____1%形式的字符串,以查找有权访问分支3,5和10的所有用户。

要构造这些字符串,最简单的方法是从一串_字符开始,这些字符与集合中最大的分支编号(或更大)一样长,然后用单个_字符替换为1个字符,然后将%附加到最后。

至于这是否比在数据库中进行循环或在应用程序中循环更快,我认为你最好的方法就是测试它。

答案 2 :(得分:2)

按位组成员资格:

从评论中,我假设我们无法使用链接表来获取组成员资格。这是一个不使用字符串的按位解决方案。它不是一个可接受的答案,因为比特数严重限制了组的数量。但是,通过使用具有显式值比较的整数,数据库可以有效地使用其索引。所以我已经添加了它适用于组/角色/任何限制足够适合的情况 PS:请原谅二进制小数混乱,我只是动态插入内容。如果我有任何错误,请随意评论和更正。

每个组都分配了一点:

G1: 0001
G2: 0010
G3: 0100
G4: 1000

用户的组成员资格是按位& 计算的。以下是二进制和十进制等值的一些示例:

U1: G1:             0001 (01)
U2: G2:             0010 (02)
U3: G3:             0100 (04)
U4: G4:             1000 (08)
U5: G1 & G2:        0011 (03)
U6: G2 & G3:        0110 (06)
U7: G1 & G3:        0101 (05)
U8: G2 & G4:        1010 (10)
U9: G1 & G2 & G4:   1011 (11)

现在,使用1-N(N是组数)的迭代进行计算,并获得任何特定组可以贡献的所有可能整数值的列表。例如, G1 将出现在任何奇数中:

G1' : 0001 (01), 0011 (03), 0101 (05), 0111 (07), 1001 (09), 1011 (11), 1101 (13), 1111 (15)
G2' : 0010 (02), 0011 (03), 0110 (06), 0111 (07), 1010 (10), 1011 (11), 1110 (14), 1111 (15)
G3' : 0100 (04), 0101 (05), 0110 (06), 0111 (07), 1100 (12), 1101 (13), 1110 (14), 1111 (15)
G4' : 1000 (08), 1001 (09), 1010 (10), 1011 (11), 1100 (12), 1101 (13), 1110 (14), 1111 (15)

您可以使用1-1000的循环执行此操作,并使用组的十进制值1,2,4,8等按位AND。 将这些值保存在内存中,或将它们推入存储组可能的组合的表中,例如 possible_memberships

Get me users in G1:
   Q: select * from users where group_memberships in (1, 3, 5, 7, 9, 11, 13, 15);
   A: U1, U5, U7, U9

Get me users in G2:
   Q: select * from users where group_memberships in (2, 3, 6, 7, 10, 11, 14, 15);
   A: U2, U5, U6, U8, U9

如果您有一个包含'possible_memberships'列的组表,您可以将值放在那里,从而节省您的时间 从必须通过线路发送所有值并允许子选择缓存在数据库上。 p为H.

Get me users in G3:
   Q: select * from users where group_memberships in (select possible_memberships from groups where name = 'G3');
   A: U3, U7, U6

答案 3 :(得分:1)

使用LIKE查询。在Sql Server中,LIKE表达式中使用的_匹配任何单个字符。要获得分支1,5和10中的用户,您可以这样:

SELECT columns FROM Users WHERE BRANCHES LIKE '1___1____1%'

这不是特别有效(它不是非常有效),但它应该可以工作,并且它可能不比你的udf选项差。

答案 4 :(得分:0)

<强>更新

这不是1000位数的可行解决方案。我会留下它,因为这篇文章中有一些选项较少的人。


您可以对数据库架构进行任何更改吗?如果您可以添加一个计算列,该列包含您在varchar中的二进制数的整数表示,那么您可以使用按位逻辑在DB中完全选择您正在谈论的内容。

以下是我所说的一个例子:

with temp as
(
   select 1 as BranchNumber -- 1
   union 
   select 2 -- 01
   union
   select 5 -- 101
   union
   select 7 -- 111
   union
   select 15 as number -- 111
)

--Select users that belong to branch 2    
    SELECT * from temp
    where (BranchNumber & 2) = 2

--returns 2,7,15



--Select users that belong to at least branches 1,2 and 3    
    SELECT * from temp
    where (BranchNumber & 7) = 7

--returns 7,15

要将二进制文件转换为数字,您可能需要创建一个可以调用以填充新列的UDF。我做了一点点探索,发现这似乎是一个很好的起点。我还没有测试过,所以要小心:

SET NOCOUNT ON
CREATE TABLE #nums (pos bigint)
DECLARE @cntr int
SET @cntr = 0
WHILE @cntr < 63 BEGIN
   INSERT INTO #nums VALUES (@cntr)
   SET @cntr = @cntr + 1
END

DECLARE @binstring varchar(63)
SET @binstring = '10000010000000001000011000000000'

SELECT
   IntegerVal =
      sum(power(convert(bigint,2),pos)
      * substring(reverse(@binstring),pos+1,1)) -- yeah, implicit conversion
   FROM #nums

DROP TABLE #nums

答案 5 :(得分:0)

虽然1000位整数不存在,但按位查询可能会胜过字符串函数。但是,因为它可以从字符串派生,您可以选择将其分解为特定数量的整数集并跨越它们进行查询。您只需要了解每列的有效位,并通过适当的常量调整输入,或者只需将其重写为表示字符串的一组位并转换为int。