T-SQL:单表包含权限树

问题描述 投票:0回答:2

我有一个 SQL Server 表,其中包含用户权限的“层次结构/树”。

每个单独权限可以有值:1 [允许]、空白 [不允许] 和 0 [明确取消]。

每个个人权限可以位于一个或多个“权限组”中,并且可以为用户分配一个或多个权限组中的所有个人权限。

每个“权限组”依次可以属于一个或多个更高级别的权限组……最终,所有权限组都位于名为“主菜单”的主组下。

此 SQL 代码:

Select 
'Main Menu' Base, 
Description Level1,
ParentId,
SecurityNodesId,
ListOrder,
Category,
LastModified

From SecurityNodes 
Where ParentId = 1
Order By Description

产生以下输出:

enter image description here

“主菜单”的 ParentId 为 NULL [屏幕截图中未显示]。

“Level1”“文件夹”包含由 SecurityNodesId 下的值“引用”的其他文件夹或个人权限。

例如,在 ParentId 列中搜索 SecurityNodesId 102 [Level1 - Administration] 将返回“Level2”下的子文件夹列表:

enter image description here

所以...我可以通过编写单独的查询来访问每个子文件夹。

但是我想要的是最终结果以表格形式显示此权限树的每个节点,如下所示:

Main Menu   Level1   Level2   Level3   Level4   PermissionName    PermissionValue

我以前从未做过如此复杂的事情,尽管我已经做了很多自连接。

我目前认为我需要对每个自连接进行自连接...以到达树的连续级别...但我相信可能有一种“递归”方法可以更有效?

如果我能得到任何帮助,我将不胜感激。

提前致谢!

sql sql-server t-sql
2个回答
0
投票

我遵循了我在原来的帖子中提到的一个想法,看起来我已经实现了我想要的。

我认为这不是最好的解决方案,因为我知道目前总共有多少个级别。如果我们突然添加一两个级别,SQL 将无法捕获所有内容,我将不得不手动添加一个或多个左连接。

Select 
'Main Menu' Base, 
sn.Description Level1,
sn2.Description Level2,
sn3.Description Level3,
sn4.Description Level4,
sn.ParentId,
sn.SecurityNodesId,
sn.ListOrder,
sn.Category,
sn.LastModified

From 
    SecurityNodes sn
    Left Join SecurityNodes sn2 On sn2.ParentId = sn.SecurityNodesId
    Left Join SecurityNodes sn3 On sn3.ParentId = sn2.SecurityNodesId
    Left Join SecurityNodes sn4 On sn3.ParentId = sn3.SecurityNodesId

Order By sn.ParentId, sn.Description

我仍然感谢任何关于以更优雅/动态的方式实现我所需要的方式的建议......但现在,上面的 SQL 正在完成这项工作。


0
投票

解决这个问题的方法是使用递归 CTE

这些绝对比您常用的 SQL 更先进,但是一旦您了解它们,它们就很容易组合在一起,并且对于分层数据(任何存储父/子关系的表)非常有用。

递归 CTE 有两个部分,由

UNION ALL
分隔。

  1. 递归种子仅运行一次并确定递归的起始结果集。对于您来说,这可能是任何

    parentId
    1
    的记录。

  2. 将 cte(本身)连接到保存父/子关系的表的递归项(或成员)。它将一遍又一遍地运行,直到 Join 或 WHERE 过滤器导致它不返回新记录。

在你的情况下,它看起来像下面这样。请注意,我不知道您的起始表是什么样的。也就是说,原始 SQL 中的

Level1
列不清楚是列名还是您称为
Level1
的别名。此外,根本不清楚如何从这些数据中得出“权限组”或“权限值”。但是……无论如何,这应该能让你进入大致范围:

WITH reccte as (
    /* 
    * To start the recursion we need a "Seed"... or a set of data
    *   that defines the starting point on which we iterate after
    *   the UNION ALL below.
    *
    * The seed here is all records with a parentid of 1
    */
    SELECT Base, 
        ParentID,
        SecurityNodesID,
        Level as Level1, 
        NULL as Level2, 
        NULL as Level3, 
        NULL as Level4,
        '?' as PermissionName,
        Category as PermissionValue,
        1 as depth, --track how deep we recurse
        Base + '>' + Level as path --keep track of where we've been and what has led us to this point in recursion
    FROM SecurityNodes

    UNION ALL

    /*
    * This section is the part that iterates. It continues to join 
    *  all rows that have been collected up that point with the Security 
    *  Nodes table until that join fails. 
    */
    
    SELECT 
        reccte.Base,
        SecurityNodes.ParentID,
        SecurityNodes.SecurityNodesID,
        reccte.Level1,
        /*
        * Depending on how deep we are in the security hierarchy
        *  capture the level string to the appropriate column
        */
        CASE WHEN depth = 1 THEN SecurityNodes.Level ELSE reccte.Level2,
        CASE WHEN depth = 2 THEN SecurityNodes.Level ELSE reccte.Level3,
        CASE WHEN depth = 3 THEN SecurityNodes.Level ELSE reccte.Level4,
        '?' as PermissionName,
        SecurityNodes.Category as PermissionValue,
        reccte.depth + 1, --increment depth
        reccte.path + '>' + SecurityNodes.Level --add to the path so we know how we got here        
    FROM reccte
        INNER JOIN SecurityNodes
            /*Join parent to child*/
            ON reccte.SecurityNodesId = SecurityNodes.parentId
    WHERE depth < 5 --Stop looking up if we go deeper than 4 levels. 
)
SELECT *
FROM reccte

虽然我们在这里跟踪

depth
并在深度达到 4 时停止递归,但您可以使用
MAXRECURSIVE
选项/提示停止递归。这将出现在您的查询末尾:

SELECT *
FROM reccte
OPTION (MAXRECURSION 4);

将任一/或添加到递归 CTE 中非常重要,否则如果安全节点的子节点也是其祖先之一,则可能会导致无限循环,从而导致其无限循环。

选项(最大递归2);

© www.soinside.com 2019 - 2024. All rights reserved.