2012-12-04 50 views
0

我喜欢组织用户管理结构,其中组织用户管理结构中组织用户例如自动位于组织单位Level 4, 3, 2, 1中。层次结构组织单位自下而上方法

正如你可以在屏幕截图中看到的。

enter image description here

每个组织单位都有它自己的ID和每个用户也是如此。所以我有一个额外的表格,用于存储每个级别的所有groupId<->userId关系。在这种情况下,我可以让所有用户例如从第3级开始,因为所有用户都使用n:n表进行关联。

这样就很容易查询用户是否在指定的组织单位中,但是......保持此结构一致性非常复杂。

示例:如果我从Level 3/Employee删除用户也必须从Level 2去除,但只有从Level 1如果此用户未处于Level B/Employee

所以问题是。 由于我无法改变用户从下往上继承组织单位的方式,因此有一种方法或设计模式,即如何实现这种方法,让用户只参考一次他的最终位置。

示例:我将用户引用到Level 5/employee,并且这种情况持久存在于数据库中,但我不想为Level 4 - 1添加关系,但如果用户是例如用户,我仍然希望快速查询。在Level 2

我不想要任何细节代码,这是一个更通用的设计问题,因为我想重新设计我的用户管理,使它更少出错。 如果我可以用SQL快速查询数据(例如使用MS SQL进行CTE查询),但它通常应该在MVC(使用RoR)环境中工作,那也是非常棒的。

如果有人需要更多的细节或信息,我会张贴ofc。

回答

0

因为我有很多答案:)我做了一些更多的调查,现在有最后的办法。

答案是嵌套树执行快速查询。

我做了什么:

创建一个用户表。

CREATE TABLE [dbo].[actor_users](
    [id] [int] NOT NULL, 
    [manager_id] [int] NULL, 
    [deputy_id] [int] NULL, 
    [username] [nvarchar](48) NOT NULL, 
    [pwd] [nvarchar](40) NULL, 
    [pwd_url] [char](38) NULL, 
    [guid] [char](38) NULL, 
    [deactivated] [smallint] NULL, 
    [lastname] [nvarchar](48) NULL, 
    [middlename] [nvarchar](48) NULL, 
    [firstname] [nvarchar](48) NULL, 
    [acronym] [nvarchar](16) NULL, 
    [employee_nr] [nvarchar](16) NULL, 
    [department] [nvarchar](250) NULL, 
    [cost_unit] [nvarchar](16) NULL, 
    [desc] [nvarchar](max) NULL, 
    [email] [nvarchar](192) NULL, 
    [sex] [int] NULL, 
    [group_ids] [nvarchar](4000) NULL, 
    [picture_id] [int] NULL, 
    [lcid] [int] NULL, 
CONSTRAINT [ct_actor_users] PRIMARY KEY CLUSTERED 
([id] ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] 

创建一个组表。

CREATE TABLE [dbo].[actor_groups](
    [id] [int] NOT NULL, 
    [parent_id] [int] NULL, 
    [group_name] [nvarchar](max) NULL, 
    [group_type] [int] NOT NULL, 
    [group_reference] [int] NOT NULL, 
    [description] [nvarchar](max) NULL, 
    [depth] [int] NULL, 
    [left] [int] NULL, 
    [right] [int] NULL, 
    [id_path] [nvarchar](max) NULL, 
    [name_path] [nvarchar](max) NULL, 
CONSTRAINT [ct_actor_groups] PRIMARY KEY CLUSTERED 
([id] ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 

我用大约10k行的随机用户填充了用户表。

现在我创造了一些随机树

-- create some random tree for testing 
DECLARE @id int; 
DECLARE @name nvarchar(max); 
WHILE (SELECT COUNT(*) FROM actor_groups)<1000 
BEGIN 
SET @id = (SELECT ISNull(MAX(id),0) + 1 FROM actors); 
SET @name = 'random_ou' + CAST(NEWID() AS nvarchar(40)); 

INSERT INTO actor_groups (id, parent_id, group_name, group_type, group_reference, [description], depth, [left], [right], id_path, name_path) 
SELECT @id 
    , (SELECT TOP 1 id FROM actor_groups ORDER BY NEWID()) AS parent_id 
    , @name group_name 
    , 3 group_type 
    , -1 group_reference 
    , '' [description] 
    , 0 depth 
    , 0 [left] 
    , 0 [right] 
    , '' id_path 
    , '' name_path 
END 

这个我必须更新组嵌套关系后....

-- update tree 
WHILE EXISTS (SELECT * FROM actor_groups WHERE depth IS NULL) 
UPDATE tr SET 
    tr.depth = par.depth + 1 , 
    tr.id_path = par.id_path + ',' + CAST(tr.id AS nvarchar(255)) , 
    tr.name_path = (CASE par.id WHEN 40 THEN '' ELSE par.name_path + '/' END) + tr.group_name 
FROM actor_groups AS tr 
INNER JOIN actor_groups AS par ON (tr.parent_id = par.id) 
WHERE par.depth >=0 AND tr.depth IS NULL 

GO 

-- left, right nested set 
WITH treerows AS 
(SELECT actor_groups.*, ROW_NUMBER() OVER (ORDER BY id_path) AS Row FROM actor_groups) 

UPDATE actor_groups 
SET [left] = tbl.Lft 
    , [right] = tbl.Rgt 
FROM actor_groups 
JOIN (SELECT 
    ER.id, 
    ER.id_path, 
    ER.depth, 
    ER.Row, 
    (ER.Row * 2) - ER.depth AS Lft, 
    ((ER.Row * 2) - ER.depth) + 
    (
     SELECT COUNT(*) * 2 
     FROM treerows ER2 
     WHERE ER2.id_path LIKE ER.id_path + ',%' 
    ) + 1 AS Rgt 
FROM treerows ER 
) tbl ON tbl.id = actor_groups.id 

现在,我做了一些随机的映射......

-- do some random mappings 
DECLARE @map int; 
DECLARE @mapuser int; 
DECLARE @counter int; 
SET @counter = 1; 
WHILE @counter<1000 
BEGIN 
    SET @map = (SELECT TOP 1 id FROM actor_groups ORDER BY NEWID()) 
    SET @mapuser = (SELECT TOP 1 id FROM actor_users ORDER BY NEWID()) 
    INSERT INTO actor_mappings ([group_id], [user_id], imported) VALUES (@map, @mapuser, 0) 
    SET @counter = @counter + 1; 
END 

所以现在我有一个组和一个用户表。 用户填满10.000个用户,我的树有大约1.000个节点。 我确实多次启动随机映射SQL,所以我有大约100.000个映射。

我的查询:

SELECT DISTINCT 
     m.[user_id] AS luserid 
    , org.[id] AS lgroupid 
    , m.imported AS bimported 
    FROM [test].[dbo].[actor_groups] org 
    JOIN [actor_groups] org2 ON org2.[left] BETWEEN org.[left] AND org.[right] 
    JOIN actor_mappings m ON org2.id = m.group_id 

查询周围700毫秒运行,如果我不缩小它。寻找一个特殊的节点或用户是在我的测试约150-300毫秒。

分辨率: 这可以使用嵌套集来完成。

使用我的1.000节点更新树就像1秒一样,查询数据时我的表上没有任何额外的索引也总是低于1秒。

希望这可以帮助其他人面临同样的问题。