层次结构组织单元自下而上的方法

时间:2012-12-04 12:43:21

标签: sql-server design-patterns

我是一个组织用户管理结构,其中组织单位位置的Level 5用户自动位于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删除。< / p>

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

示例:我将用户引用到Level 5/employee并且这是在数据库中保留的,但我不想为Level 4 - 1添加关系,但我仍然想要快速查询用户是否例如在Level 2

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

如果有人需要更多详细信息或信息,我会发布信息。

1 个答案:

答案 0 :(得分: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秒。

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