我有一个非常典型的情况。我们有一个名为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根据分支过滤此结果集,然后返回结果。
答案 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。