TSQL CTE: Как избежать кругового обхода?
Я написал очень простое выражение CTE, которое извлекает список всех групп, членом которых является пользователь.
Правила выполняются следующим образом: пользователь может быть в нескольких группах, а группы могут быть вложены так, что группа может быть членом другой группы, и, кроме того, группы могут быть взаимными членами другого, поэтому группа A является член группы B и группа B также является членом группы A.
My 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
)
Я не могу найти легкое решение для отслеживания тех групп, которые я уже записал.
Я думал об использовании дополнительного параметра varchar в CTE для записи списка всех групп, которые я посетил, но использование varchar слишком грубо, не так ли?
Есть ли лучший способ?
Ответы
Ответ 1
Вам нужно скопировать строку часового в вашей рекурсии. В следующем примере у меня есть круговое отношение от 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
Ответ 2
Вместо строки дозорного, используйте переменную таблицы сторожевого устройства. Функция будет ломать круговую ссылку независимо от того, сколько хэпов кружок, никаких проблем с максимальной длиной nvarchar (макс.), Легко модифицируется для разных типов данных или даже с несколькими ключами, и вы можете назначить функцию контрольному ограничению.
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