2012-06-14 129 views
11

我寫了一個非常簡單的CTE表達式,用於檢索用戶所屬的所有組的列表。TSQL 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太簡單了,不是嗎?

有沒有更好的方法?

+0

你確定它是永遠遞歸嗎?服務器默認值是100次迭代。嘗試閱讀[MSDN]上的MAXRECURSION提示(http://msdn.microsoft.com/zh-cn/library/ms175972.aspx)。 – Bridge

+0

首先擔心功效,*然後*擔心粗糙,如果時間允許的話:) – AakashM

+0

它不會永久遞歸,因爲它會在100次遞歸調用後引發錯誤。原諒我的措辭。 – Haoest

回答

25

您需要在遞歸中累積一個哨兵字符串。在下面的例中,有從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 
+1

我喜歡你的解決方案,因爲它的工作原理。但有沒有辦法做到這一點,沒有哨兵字符串?我覺得這是笨重和重複的,我們必須在每個哨兵條目周圍添加一些分隔符,比如 Sentinel ='<'+ CAST(Parent AS VARCHAR(MAX))+'>' 然後我們必須做在CharIndex()函數中也是如此,因爲如果沒有分隔符,可能會出現誤報。 如果哨兵字符串變得太大以至於超過了varchar(max)的長度會發生什麼? – Haoest

+2

我很高興聽到這個作品。這有點破解,我實在想不出一種「更清潔」的方式。但是,請記住,標記沿每個遞歸分支獨立增長,因此只能得到每個字符串的最大深度時間以及分隔符的大小。 VARCHAR(MAX)有2GB的限制,而最大深度可以放大,如果有必要的話,最大爲32767.所以,你很可能會溢出VARCHAR(MAX)。大多數遞歸作業可能有幾千棵樹,但其深度很少超過5個左右。所以,你的哨兵字符串通常會保持相當小。 –

+0

很高興知道,謝謝。 – Haoest

2

而不是一個哨兵字符串,使用一個sentinel表變量。函數將捕獲循環引用,無論該圓圈有多少跳,沒有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