SQL 中的递归 - 公共表表达式与 WHILE

Dai*_*Dai 3 sql t-sql recursion common-table-expression

我有这张桌子:

Categories( CatId, Name, ParentId NULL )
Run Code Online (Sandbox Code Playgroud)

...这是递归的,因此每个类别都可以命名一个父类别,该父类别又具有一个父类别,依此类推。像这样:

1, "Sports", NULL
2, "Football", 1
3, "Golf", 1
4, "Handegg", 2
5, "Sex", NULL
6, "On the beach", 5
Run Code Online (Sandbox Code Playgroud)

不久前,我使用 CTE 对类别的父级执行递归查找,如下所示:

WITH Categories(CatId, Name, ParentId, n) AS (
    SELECT CatId, Name, ParentId, 1
    FROM Categories
    WHERE CatId = @categoryId

    UNION ALL

    SELECT c1.CatId, c1.Name, c1.ParentId, c2.n + 1
    FROM Categories AS c1
         INNER JOIN Categories AS c2 ON c1.CatId = c2.ParentId
)
Run Code Online (Sandbox Code Playgroud)

但我在想,我不能把它改写为 WHILE 查询吗?

DECLARE @ret TABLE( CatId, Name, ParentId )

DECLARE @tCatId int
DECLARE @tName nvarchar(255)
DECLARE @tParentId int NULL

SELECT @tCatId = CatId, @tName = Name, @tParentId = ParentId
FROM Categories
WHERE CatId = @categoryId

WHILE( @tParentId IS NOT NULL ) BEGIN
    INSERT INTO @ret ( CatId, Name, ParentId ) VALUES ( @tCatId, @tName, @tParentId )

    SELECT @tCatId = CatId, @tName = Name, @tParentId = ParentId
    FROM Categories
    WHERE CatId = @tParentId
END

SELECT @ret
Run Code Online (Sandbox Code Playgroud)

显然,它的边缘有点粗糙(例如,当第一行的 ParentId 为 NULL 时未完成),并且我无法对其进行测试(因为我的 SQL Server 已关闭以进行重建),但它肯定是正确的?

usr*_*usr 5

虽然这在语义上是可行的,但 CTE 通常更适合编程,因为它们代表不可变的逻辑结果集。while 循环强制规定了执行模型。出于这个原因,正确和维护需要更多的工作。

CTE 很有可能会更快。它在内部包含一种形式的 while 循环,但该循环位于查询执行管道的深处。它比 T-SQL 循环快得多。此外,它是一个单一的声明。while 循环运行许多语句。SQL Server 的每个语句开销很小(即使它只是select null)。

也就是说,while 循环有时会为您提供更大的灵活性和更多的控制权。

您可能应该首先尝试使用 CTE 解决您的要求。仅当您注意到(或您可以预见)由于某些具体原因,while 循环将是更好的解决方案时,才使用命令式控制流。