適用於權限管理系統 (角色嵌套)、ERP BOM 展開、組織架構遍歷等樹狀/圖狀結構查詢。
dbo.fn_GetUserInheritanceTreeCREATE FUNCTION dbo.fn_GetUserInheritanceTree
(
@UserName SYSNAME, -- 起始使用者/角色名稱
@Direction BIT -- 0 = 向下查詢子孫, 1 = 向上查詢祖先
)
RETURNS @Result TABLE
(
UserID SYSNAME, -- principal_id
UserName SYSNAME, -- name
LevelDepth INT -- 階層深度 (1=起始節點)
)
AS
BEGIN
DECLARE @StartPrincipalID INT = USER_ID(@UserName);
IF @StartPrincipalID IS NULL RETURN; -- 使用者不存在
DECLARE @Stack TABLE (PrincipalID INT PRIMARY KEY, LevelDepth INT);
INSERT @Stack VALUES (@StartPrincipalID, 1);
WHILE EXISTS (SELECT 1 FROM @Stack)
BEGIN
-- 取得當前層級最小 LevelDepth 的節點 (廣度優先) 或任一節點
DECLARE @CurrentID INT, @CurrentLevel INT;
SELECT TOP 1 @CurrentID = PrincipalID, @CurrentLevel = LevelDepth
FROM @Stack ORDER BY LevelDepth; -- BFS 順序
-- 輸出結果
INSERT @Result VALUES (@CurrentID, USER_NAME(@CurrentID), @CurrentLevel);
-- 從堆疊移除
DELETE FROM @Stack WHERE PrincipalID = @CurrentID;
-- 尋找下一層節點
IF @Direction = 1 -- 向上找祖先: 當前是成員,找群組
INSERT @Stack
SELECT rm.role_principal_id, @CurrentLevel + 1
FROM sys.database_role_members rm
WHERE rm.member_principal_id = @CurrentID
AND NOT EXISTS (SELECT 1 FROM @Stack s WHERE s.PrincipalID = rm.role_principal_id)
AND NOT EXISTS (SELECT 1 FROM @Result r WHERE r.UserID = USER_NAME(rm.role_principal_id));
ELSE -- 向下找子孫: 當前是群組,找成員
INSERT @Stack
SELECT rm.member_principal_id, @CurrentLevel + 1
FROM sys.database_role_members rm
WHERE rm.role_principal_id = @CurrentID
AND NOT EXISTS (SELECT 1 FROM @Stack s WHERE s.PrincipalID = rm.member_principal_id)
AND NOT EXISTS (SELECT 1 FROM @Result r WHERE r.UserID = USER_NAME(rm.member_principal_id));
END
RETURN;
END
-- 建立測試角色階層: Users > BusinessMan > Saler > OrderMan
EXEC sp_addrole 'Users';
EXEC sp_addrole 'BusinessMan'; EXEC sp_addrolemember 'Users', 'BusinessMan';
EXEC sp_addrole 'Saler'; EXEC sp_addrolemember 'BusinessMan', 'Saler';
EXEC sp_addlogin 'OrderMan', 'Pwd123', 'mydb'; EXEC sp_grantdbaccess 'OrderMan';
EXEC sp_addrolemember 'Saler', 'OrderMan';
-- 查詢 OrderMan 的所有祖先角色 (向上)
SELECT * FROM dbo.fn_GetUserInheritanceTree('OrderMan', 1);
-- 結果:
-- UserID UserName LevelDepth
-- 5 OrderMan 1
-- 102 Saler 2
-- 103 BusinessMan 3
-- 104 Users 4
-- 查詢 Users 角色包含的所有子孫成員 (向下)
SELECT * FROM dbo.fn_GetUserInheritanceTree('Users', 0);
-- 使用遞迴 CTE (Common Table Expression) 更簡潔、集合導向、優化器友善
WITH cte AS (
SELECT principal_id, name, 1 AS lvl, CAST(name AS VARCHAR(MAX)) AS path
FROM sys.database_principals WHERE name = 'OrderMan'
UNION ALL
SELECT p.principal_id, p.name, cte.lvl + 1, cte.path + ' > ' + p.name
FROM sys.database_principals p
JOIN sys.database_role_members rm ON p.principal_id = rm.role_principal_id
JOIN cte ON rm.member_principal_id = cte.principal_id
WHERE cte.lvl < 100 -- 防無限遞迴
)
SELECT principal_id AS UserID, name AS UserName, lvl AS LevelDepth FROM cte;
推薦:新專案優先使用遞迴 CTE (可讀性、效能、平行化優於 WHILE 迴圈)。函數封裝適合舊版相容或需參數化複用的場景。