12

我昨天回答了一个递归 CTE,它暴露了在 SQL Server 中实现这些方法的问题(也可能在其他 RDBMS 中?)。基本上,当我尝试ROW_NUMBER针对当前递归级别使用时,它会针对当前递归级别的每一行子集运行。我希望这将在真正的 SET 逻辑中工作,并针对整个当前递归级别运行。

从这篇 MSDN 文章看来,我发现的问题是预期的功能:

CTE 递归部分中的分析和聚合函数应用于当前递归级别的集合,而不是应用于 CTE 的集合。像 ROW_NUMBER 这样的函数只对当前递归级别传递给它们的数据子集进行操作,而不是传递给 CTE 递归部分的整个数据集。有关更多信息,请参阅 J. 在递归 CTE 中使用分析函数。

在我的挖掘中,我找不到任何地方可以解释为什么选择它以它的方式工作?这更像是基于集合的语言中的一种程序方法,因此这与我的 SQL 思维过程相悖,并且在我看来非常令人困惑。有人知道和/或有人可以解释为什么递归 CTE 以程序方式在递归级别处理分析函数吗?


这是帮助可视化的代码:

请注意,RowNumber每个代码输出中的列。

这是 CTE 的 SQLFiddle(仅显示第二级递归)

WITH myCTE
AS
(
  SELECT *, ROW_NUMBER() OVER (ORDER BY Score desc) AS RowNumber, 1 AS RecurseLevel
  FROM tblGroups
  WHERE ParentId IS NULL

  UNION ALL

  SELECT tblGroups.*, 
      ROW_NUMBER() OVER (ORDER BY myCTE.RowNumber , tblGroups.Score desc) AS RowNumber, 
      RecurseLevel + 1 AS RecurseLevel
  FROM tblGroups
      JOIN myCTE
          ON myCTE.GroupID = tblGroups.ParentID
 )
SELECT *
FROM myCTE
WHERE RecurseLevel = 2;

这是我期望 CTE 做的第二个 SQLFiddle(再次只需要第二级来显示问题)

WITH myCTE
AS
(
  SELECT *, ROW_NUMBER() OVER (ORDER BY Score desc) AS RowNumber, 1 AS RecurseLevel
  FROM tblGroups
  WHERE ParentId IS NULL
 )
  SELECT tblGroups.*, 
      ROW_NUMBER() OVER (ORDER BY myCTE.RowNumber , tblGroups.Score desc) AS RowNumber, 
      RecurseLevel + 1 AS RecurseLevel
  FROM tblGroups
      JOIN myCTE
          ON myCTE.GroupID = tblGroups.ParentID;

我一直设想 SQL 递归 CTE 运行得更像这样 while 循环

DECLARE @RecursionLevel INT
SET @RecursionLevel = 0
SELECT *, ROW_NUMBER() OVER (ORDER BY Score desc) AS RowNumber, @RecursionLevel AS recurseLevel
INTO #RecursiveTable
FROM tblGroups
WHERE ParentId IS NULL

WHILE EXISTS( SELECT tblGroups.* FROM tblGroups JOIN #RecursiveTable ON #RecursiveTable.GroupID = tblGroups.ParentID WHERE recurseLevel = @RecursionLevel)
BEGIN

    INSERT INTO #RecursiveTable
    SELECT tblGroups.*, 
        ROW_NUMBER() OVER (ORDER BY #RecursiveTable.RowNumber , tblGroups.Score desc) AS RowNumber, 
        recurseLevel + 1 AS recurseLevel
    FROM tblGroups
        JOIN #RecursiveTable
            ON #RecursiveTable.GroupID = tblGroups.ParentID
    WHERE recurseLevel = @RecursionLevel
    SET @RecursionLevel = @RecursionLevel + 1
END

SELECT * FROM #RecursiveTable ORDER BY RecurseLevel;
4

1 回答 1

1

分析函数的特殊之处在于它们需要已知的结果集来解析。它们依赖于后面的、前面的或完整的结果集来计算当前值。也就是说,在包含分析函数的视图上永远不允许合并视图。为什么?那会改变结果。

前任:

    Select * from (
      select row_number() over (partition by c1 order by c2) rw, c3 from t) z
    where c3=123

不一样

    select row_number() over (partition by c1 order by c2) rw, c3 from t 
    where c3=123

这两个将为 rw 返回不同的值。这就是为什么包含分析函数的子查询将始终在之前完全解析并且永远不会与其余的合并。

更新

查看第二个查询:

WITH myCTE
AS
(
  SELECT *, ROW_NUMBER() OVER (ORDER BY Score desc) AS RowNumber, 1 AS RecurseLevel
  FROM tblGroups
  WHERE ParentId IS NULL
 )
  SELECT tblGroups.*, 
      ROW_NUMBER() OVER (ORDER BY myCTE.RowNumber , tblGroups.Score desc) AS RowNumber, 
      RecurseLevel + 1 AS RecurseLevel
  FROM tblGroups
      JOIN myCTE
          ON myCTE.GroupID = tblGroups.ParentID;

它的工作原理就像它被写成一样(相同的执行计划和结果):

SELECT tblGroups.*, 
      ROW_NUMBER() OVER (ORDER BY myCTE.RowNumber , tblGroups.Score desc) AS RowNumber, 
      RecurseLevel + 1 AS RecurseLevel
FROM tblGroups
JOIN (
    SELECT *, ROW_NUMBER() OVER (ORDER BY Score desc) AS RowNumber, 1 AS RecurseLevel
    FROM tblGroups
    WHERE ParentId IS NULL
    )myCTE ON myCTE.GroupID = tblGroups.ParentID;

这个需要分区来重置行号。

递归查询在 while 循环中不起作用,它们不是程序性的。在基础上,它们像递归函数一样工作,但根据表、查询、索引,它们可以被优化为以一种或另一种方式运行。

如果我们确实遵循在使用分析函数时无法合并视图的概念,并且查看查询1。它只能运行一次,并且处于嵌套循环中。

WITH myCTE
AS
( /*Cannot be merged*/
  SELECT *, ROW_NUMBER() OVER (ORDER BY Score desc) AS RowNumber, 1 AS RecurseLevel,
  cast(0 as bigint) n
  FROM tblGroups
  WHERE ParentId IS NULL

  UNION ALL

/*Cannot be merged*/
  SELECT tblGroups.*, 
      ROW_NUMBER() OVER (ORDER BY myCTE.RowNumber, tblGroups.Score desc) AS RowNumber,       RecurseLevel + 1 AS RecurseLevel,
  myCTE.RowNumber
  FROM tblGroups
      JOIN myCTE
          ON myCTE.GroupID = tblGroups.ParentID
 )
SELECT *
FROM myCTE;

所以第一个选择,第二个也不能合并。运行此查询的唯一方法是在每个级别中返回的每个项目的嵌套循环中,因此重置。同样,这不是程序与否的问题,只是可能的执行计划的问题。

希望这能回答您的问题,如果没有,请告诉我:)

是的

于 2012-04-14T14:43:23.770 回答