I have written a very simple CTE expression that retrieves a list of all groups of which a user is a member.
The rules goes like this, a user can be in multiple gro
You need to accumulate a sentinel string within your recursion. In the following example I have a circular relationship from A,B,C,D, and then back to A, and I avoid a loop with the sentinel string:
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;
Result:
Parent Child Sentinel
------ ----- --------
A B A
B C A|B
C D A|B|C
D A A|B|C|D
Instead of a sentinel string, use a sentinel table variable. Function will catch circular reference no matter how many hops the circle is, no issues with maximum length of nvarchar(max), easily modified for different data types or even multipart keys, and you can assign the function to a check constraint.
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)
RETURN 1;
INSERT INTO @Sentinel
( [ID] )
VALUES ( @NextAccountID )
END
RETURN 0;
END