TSQL CTE:如何避免循环遍历?

Hao*_*est 11 t-sql common-table-expression

我编写了一个非常简单的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
            )
Run Code Online (Sandbox Code Playgroud)

我找不到一个简单的解决方案来回溯我已记录的那些组.

我在考虑在CTE中使用额外的varchar参数来记录我访问过的所有组的列表,但是使用varchar太粗糙了,不是吗?

有没有更好的办法?

Joh*_*wey 25

您需要在递归中累积一个标记字符串.在下面的例子中,我有一个从A,B,C,D到A的循环关系,我避免使用sentinel字符串的循环:

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;
Run Code Online (Sandbox Code Playgroud)

结果:

Parent Child Sentinel
------ ----- --------
A      B     A
B      C     A|B
C      D     A|B|C
D      A     A|B|C|D
Run Code Online (Sandbox Code Playgroud)

  • 我认为你必须以不同方式构建sentinel字符串,以避免在一般情况下出现误报(当不使用CHAR(1)时).CHARINDEX可能会在`AB | C`中找到`A`,但在`<AB> <C>`中找不到`<A>`.此外,如果允许ID包含<或>,您也需要正确编码.当然,如果你继续使用CHAR(1),这都不是问题,但这不是一个现实的案例.无论如何,优秀的想法和我的+1! (4认同)
  • 我喜欢你的解决方案,因为它有效。但是有没有办法在没有哨兵字符串的情况下做到这一点?我觉得我们必须在每个哨兵条目周围添加某种分隔符,例如 Sentinel = '&lt;' + CAST(Parent AS VARCHAR(MAX)) + '&gt;' 然后我们必须在 CharIndex( ) 函数,因为如果没有分隔符,可能会出现误报。如果哨兵字符串变得如此之大以至于超过了 varchar(max) 的长度,会发生什么? (2认同)
  • 我很高兴听到这个有效.这有点像黑客,老实说,我想不出一种"更干净"的方式.但是,请记住,哨兵独立地沿每个递归分支增长,因此只会达到每个字符串的最大深度倍数加上分隔符.VARCHAR(MAX)的限制为2 GB,如果需要,最大深度可以扩大到最大值32767.因此,您很可能不会溢出VARCHAR(MAX).大多数递归工作可能有几千棵树,但其深度很少超过5左右.所以,你的哨兵字符串通常会相当小. (2认同)