用于检索路径的公用表表达式

时间:2015-03-30 19:01:45

标签: sql-server recursion common-table-expression

我正在尝试使用递归查询来查找通过如下结构的表格的路径:

RelatedEntities

FromKey TINYINT
ToKey TINYINT
...more....

我以为我可以这样做:

DECLARE @startKey UNIQUEIDENTIFIER, @endKey UNIQUEIDENTIFIER;
SET @startKey = 0;
SET @endKey = 3;

;With findPath
AS
(
    SELECT FromKey, ToKey
    FROM RelatedEntities
    WHERE FromKey = @startKey

    UNION ALL

    SELECT FromKey, ToKey
    FROM RelatedEntities r
    JOIN findPath b
    ON r.FromKey = b.ToKey
    AND r.FromKey NOT IN (SELECT FromKey FROM b)
)
SELECT * FROM findPath;

此代码失败,因为我无法在CTE中使用子查询。似乎规则是递归查询只能包含对CTE的一个引用。 (是吗?)也许这是光标或程序代码的工作,但我想我会把它放在这里,以防我找不到通过CTE表找到路径的方法?

参数是:

  1. 以开始和结束键开头
  2. 基本查询使用开始键
  3. 递归查询应该在包含结束键时停止((无法确定那个)并且不应重复启动键。
  4. MAXRECURSION选项可用于在一定次数的迭代后停止。
  5. 感谢各位CTE大师。让我直截了当。

    将此值从UNIQUEIDENTIFIERS更改为TINYINT以提高可读性。 SQL结构是相同的。这是一些测试它的代码。

    CREATE TABLE RelatedEntities(FromKey TINYINT, ToKey TINYINT);
    
    INSERT RelatedEntities(FromKey, ToKey)
    VALUES
    (1, 0),
    (0, 1), 
    (1, 7),
    (7, 1),
    (3, 4),
    (4, 3)
    
    ;With FindPath 
    AS
    (
        SELECT FromKey, ToKey, 0 AS recursionLevel
        FROM RelatedEntities
        WHERE FromKey = 1
    
        UNION ALL
    
        SELECT r.FromKey, r.ToKey, recursionLevel = recursionLevel + 1
        FROM RelatedEntities r
        INNER JOIN FindPath b ON r.FromKey = b.ToKey
        WHERE b.ToKey <> 3 AND RecursionLevel < 10
    )
    SELECT * FROM FindPath
    ORDER BY recursionLevel
    

    请注意,这会从1返回0,然后从0返回到1,并重复直到我用完递归级别。

1 个答案:

答案 0 :(得分:2)

您需要像这样修改您的查询:

DECLARE @startKey UNIQUEIDENTIFIER, @endKey UNIQUEIDENTIFIER;
DECLARE @maxRecursion INT = 100
SET @startKey = '00000000-0000-0000-0000-000000000000';
SET @endKey = 'F7801327-C037-AA93-67D1-B7892F6093A7';

;With FindPath
AS
(
    SELECT FromKey, ToKey, 0 AS recursionLevel
    FROM RelatedEntities
    WHERE FromKey = @startKey

    UNION ALL

    SELECT r.FromKey, r.ToKey, recursionLevel = recursionLevel +1
    FROM RelatedEntities r
    INNER JOIN FindPath b ON r.FromKey = b.ToKey
    WHERE b.ToKey <> @endKey AND recursionLevel < @maxRecursion
)
SELECT * FROM FindPath;

以上CTE锚点成员

SELECT FromKey, ToKey, 0 AS recursionLevel
FROM RelatedEntities
WHERE FromKey = @startKey

将选择(From,To)记录链的起始记录T0

CTE的递归成员:

SELECT r.FromKey, r.ToKey, recursionLevel = recursionLevel +1
FROM RelatedEntities r
INNER JOIN FindPath b ON r.FromKey = b.ToKey
WHERE b.ToKey <> @endKey AND recursionLevel < @maxRecursion

将以T0T1,...作为输入执行,T1T2,...分别作为输出执行。

此过程将继续向最终结果集添加记录,直到从递归成员返回空集,即直到将ToKey=@endKey的记录添加到结果集或@maxRecursion级别。

修改

您可以使用以下查询来有效处理任何循环路径:

;With FindPath 
AS
(
    SELECT FromKey, ToKey, 
           0 AS recursionLevel,
           CAST(FromKey AS VARCHAR(MAX)) AS FromKeys
    FROM RelatedEntities
    WHERE FromKey = 1

    UNION ALL

    SELECT r.FromKey, r.ToKey, 
           recursionLevel = recursionLevel + 1,
           FromKeys = FromKeys + ',' + CAST(r.FromKey AS VARCHAR(MAX))
    FROM RelatedEntities r
    INNER JOIN FindPath b ON r.FromKey = b.ToKey
    WHERE (b.ToKey <> 3) 
          AND (RecursionLevel < 10) 
          AND PATINDEX('%,' + CAST(r.ToKey AS VARCHAR(MAX)) + ',%', ',' + FromKeys + ',') = 0  
)
SELECT * FROM FindPath
ORDER BY recursionLevel

计算字段FromKeys用于将FromKey传递到下一个递归级别。这样,来自先前递归级别的任何键都使用字符串连接从一个级别累积到另一个级别。然后使用PATINDEX检查是否已满足循环路径。

SQL Fiddle Demo here