我有一个 SQL Server 表,其中包含用户权限的“层次结构/树”。
每个单独权限可以有值:1 [允许]、空白 [不允许] 和 0 [明确取消]。
每个个人权限可以位于一个或多个“权限组”中,并且可以为用户分配一个或多个权限组中的所有个人权限。
每个“权限组”依次可以属于一个或多个更高级别的权限组……最终,所有权限组都位于名为“主菜单”的主组下。
此 SQL 代码:
Select
'Main Menu' Base,
Description Level1,
ParentId,
SecurityNodesId,
ListOrder,
Category,
LastModified
From SecurityNodes
Where ParentId = 1
Order By Description
产生以下输出:
“主菜单”的 ParentId 为 NULL [屏幕截图中未显示]。
“Level1”“文件夹”包含由 SecurityNodesId 下的值“引用”的其他文件夹或个人权限。
例如,在 ParentId 列中搜索 SecurityNodesId 102 [Level1 - Administration] 将返回“Level2”下的子文件夹列表:
所以...我可以通过编写单独的查询来访问每个子文件夹。
但是我想要的是最终结果以表格形式显示此权限树的每个节点,如下所示:
Main Menu Level1 Level2 Level3 Level4 PermissionName PermissionValue
我以前从未做过如此复杂的事情,尽管我已经做了很多自连接。
我目前认为我需要对每个自连接进行自连接...以到达树的连续级别...但我相信可能有一种“递归”方法可以更有效?
如果我能得到任何帮助,我将不胜感激。
提前致谢!
我遵循了我在原来的帖子中提到的一个想法,看起来我已经实现了我想要的。
我认为这不是最好的解决方案,因为我知道目前总共有多少个级别。如果我们突然添加一两个级别,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 正在完成这项工作。
解决这个问题的方法是使用递归 CTE。
这些绝对比您常用的 SQL 更先进,但是一旦您了解它们,它们就很容易组合在一起,并且对于分层数据(任何存储父/子关系的表)非常有用。
递归 CTE 有两个部分,由
UNION ALL
分隔。
递归种子仅运行一次并确定递归的起始结果集。对于您来说,这可能是任何
parentId
为 1
的记录。
将 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);