12

ユーザーがメンバーであるすべてのグループのリストを取得する非常に単純な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を使用するのはあまりにも粗雑ですよね?

もっと良い方法はありますか?

4

2 に答える 2

27

再帰内にセンチネル文字列を蓄積する必要があります。次の例では、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
于 2012-06-14T22:06:01.390 に答える
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
于 2013-09-27T20:29:40.503 に答える