2012-06-14 3 views
11

私は、ユーザーがメンバーであるすべてのグループのリストを取得する非常に単純なCTE式を書いています。TSQL 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 
      ) 

私はバックする簡単な解決策を見つけることができません:グループBとグループBのメンバーも、私のCTEはこのようになり、明らかにそれは無限再帰をもたらし

グループAのメンバーでありますすでに録音したグループを追跡します。

私は訪問したすべてのグループのリストを記録するためにCTEに追加のvarcharパラメータを使用することを考えていましたが、varcharを使用するのはあまりにも粗すぎるのですか?

良い方法がありますか?

+0

あなたは永遠に再帰していますか?サーバーのデフォルトは100回です。 [MSDN](http://msdn.microsoft.com/en-us/library/ms175972.aspx)の「MAXRECURSION」ヒントをお読みください。 – Bridge

+0

最初に有効性について心配し、時間が許せば* crudnessについて心配してください:) – AakashM

+0

100回の再帰呼び出しの後にエラーを投げるので永遠に再帰しません。私の言葉を許してください。 – Haoest

答えて

25

再帰内にセンチネル文字列を蓄積する必要があります。次の例では、私はA、B、C、Dから円形の関係を有し、再びAに、そしてIは、センチネル文字列でループを避ける:

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 
+1

私はあなたの解決策が好きです。しかし、センチネル文字列なしでこれを行う方法はありますか? Sentinel = '<' + CAST(親AS VARCHAR(MAX))+ '>' 次に、私たちはCharIndex()関数でも同じです。デリミタがないと誤検出が起こる可能性があるからです。 そして、センチネル文字列が非常に大きくなってvarchar(max)の長さを超えるとどうなりますか? – Haoest

+2

これはうれしいです。それはちょっとしたハックですが、私は正直言って「クリーナー」の方法を考えることはできません。しかし、センチネルは独立して各再帰ブランチに沿って成長するので、各文字列の最大深さと区切り文字の最大深さのように大きくなります。 VARCHAR(MAX)の上限は2 GBですが、最大深度は必要に応じて最大32767まで拡大できます。したがって、VARCHAR(MAX)をオーバーフローさせる可能性は非常に低いです。ほとんどの再帰ジョブには数千の木があるかもしれませんが、深さがまれに5を超えることはほとんどありません。だから、あなたのセンチネルの弦は、一般的にかなり小さいままです。 –

+0

おかげで、ありがとう。 – Haoest

2

をセンチネル文字列の代わりに、センチネル表変数を使用します。関数は円の数に関係なく循環参照をキャッチし、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) 
      RETURN 1; 
     INSERT INTO @Sentinel 
       ([ID]) 
     VALUES  (@NextAccountID) 
    END 
    RETURN 0; 
END