我编写了一个非常简单的 CTE 表达式,用于检索用户所属的所有组的列表。
规则是这样的,一个用户可以在多个组中,并且组可以嵌套,这样一个组可以是另一个组的成员,而且,组可以是另一个组的成员,因此组 A 是B组,B组也是A组的成员。
我的 CTE 是这样的,显然它会产生无限递归:
;WITH GetMembershipInfo(entityId) AS( -- entity can be a user or group
SELECT k.ID as entityId FROM entities k WHERE k.id = @userId
UNION ALL
SELECT k.id FROM entities k
JOIN Xrelationships kc on kc.entityId = k.entityId
JOIN GetMembershipInfo m on m.entityId = kc.ChildID
)
我找不到简单的解决方案来回溯我已经录制的那些组。
我正在考虑在 CTE 中使用额外的 varchar 参数来记录我访问过的所有组的列表,但是使用 varchar 太粗糙了,不是吗?
有更好的方法吗?
您需要在递归中积累一个哨兵字符串。在下面的示例中,我有一个从 A、B、C、D 到 A 的循环关系,并且我避免了使用哨兵字符串的循环:
DECLARE @MyTable TABLE(Parent CHAR(1), Child CHAR(1));
INSERT @MyTable VALUES('A', 'B');
INSERT @MyTable VALUES('B', 'C');
INSERT @MyTable VALUES('C', 'D');
INSERT @MyTable VALUES('D', 'A');
; WITH CTE (Parent, Child, Sentinel) AS (
SELECT Parent, Child, Sentinel = CAST(Parent AS VARCHAR(MAX))
FROM @MyTable
WHERE Parent = 'A'
UNION ALL
SELECT CTE.Child, t.Child, Sentinel + '|' + CTE.Child
FROM CTE
JOIN @MyTable t ON t.Parent = CTE.Child
WHERE CHARINDEX(CTE.Child,Sentinel)=0
)
SELECT * FROM CTE;
结果:
Parent Child Sentinel
------ ----- --------
A B A
B C A|B
C D A|B|C
D A A|B|C|D
使用哨兵表变量代替哨兵字符串。无论圆有多少跳,函数都会捕获循环引用,nvarchar(max) 的最大长度没有问题,可以轻松修改不同的数据类型甚至多部分键,并且您可以将函数分配给检查约束。
CREATE FUNCTION [dbo].[AccountsCircular] (
@AccountID UNIQUEIDENTIFIER
)
RETURNS BIT
AS
BEGIN
DECLARE @NextAccountID UNIQUEIDENTIFIER = NULL;
DECLARE @Sentinel TABLE (
ID UNIQUEIDENTIFIER
);
INSERT INTO @Sentinel ([ID])
VALUES (@AccountID);
SET @NextAccountID = @AccountID;
WHILE @NextAccountID IS NOT NULL BEGIN
SELECT @NextAccountID = [ParentAccountID]
FROM [dbo].[Accounts]
WHERE [AccountID] = @NextAccountID;
IF EXISTS (SELECT 1 FROM @Sentinel WHERE ID = @NextAccountID) BEGIN
RETURN 1;
END;
INSERT INTO @Sentinel ([ID])
VALUES (@NextAccountID)
END;
RETURN 0;
END;