【问题标题】:Why does a Recursive CTE in Transact-SQL require a UNION ALL and not a UNION?为什么 Transact-SQL 中的递归 CTE 需要 UNION ALL 而不是 UNION?
【发布时间】:2018-06-08 11:47:36
【问题描述】:

我知道锚是必要的,这是有道理的。而且我知道需要UNION ALL,如果您的递归CTE 没有,它就是行不通的......但我找不到一个很好的解释为什么会这样。所有文档都只是说明您需要它。

为什么不能在递归查询中使用UNION 而不是UNION ALL?似乎在更深层次的递归中不包含重复项是个好主意,不是吗?我想,类似的东西应该已经在幕后工作了。

【问题讨论】:

  • 我们可以看看一些代码吗,因为我不明白你的意思。
  • 反对者是否愿意发表评论?
  • @Sami 递归 CTE 的定义需要锚查询 UNION ALL'd 到递归查询。我在问为什么它需要 UNION ALL 而不是 UNION,我想优化器可能已经以某种形式在做。
  • 因为这是语法。如果您的代码有问题,有重复的项目,请发布
  • @Ezin82 我的代码没有问题,而且我知道语法。我知道如何在 SQL 中递归。我在问为什么当递归查询似乎想要控制重复项以保持一定程度的性能时,为什么需要这种机制,即这是任意选择的,是否尝试保持一些 ANSI SQL 兼容性,是否存在技术限制,等

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


【解决方案1】:

这纯粹是推测,但我想说的是,UNION ALL 可以确保每次迭代的结果都可以单独计算。从本质上讲,它确保了一个迭代不会干扰另一个迭代。

UNION 需要在后台进行排序操作,这可能会修改先前迭代的结果。程序不应更改调用堆栈中先前调用的状态,它应使用输入参数和后续迭代的结果(在程序设置中)与其交互。这可能应该适用于基于集合的操作,因此适用于 SQL Server 的递归 CTE。

我可能错了,深夜的大脑转储不是 100% 可靠的 :)

编辑(只是另一个想法):

当递归开始时,你有一个调用堆栈。此堆栈中的每个级别都开始计算其结果,但应等待所有后续调用的结果才能完成并返回其结果。 UNION 会尝试消除重复,但是在达到终止条件之前您没有任何记录(并且最终会从底部到顶部构建),但是后续调用的结果是它上面的调用的结果. UNION 将在最后减少为 DISTINCT。

【讨论】:

  • 不是每个递归在每次调用时都添加到堆栈中,并在完成时将其自身删除吗?如果是这样的话,在我看来,它会随着它进行重复数据删除。
  • 我不确定这是否是最好的解决方案。这意味着,排序和比较必须在每次迭代之后进行。我会说,在很多情况下(当然不是全部),在计算最终结果之后执行它会更高效。编辑中的调用堆栈源自过程递归的工作方式,当涉及到 SQL Server 及其优化器时,我可能是错的。
  • 最后做的不一样。它可以区分终止或无限循环。
【解决方案2】:

我认为原因是他们只是不认为这是一个值得实施的优先功能。它看起来像 Postgres does support both UNIONUNION ALL

如果您对此功能有充分的理由,您可以在Connect(或任何替代网址)提供反馈。

防止添加重复项可能很有用,因为在后续步骤中添加到前一个步骤中的重复行几乎总是会导致无限循环或超过最大递归限制。

SQL Standards中有不少地方使用了演示UNION的代码,如下所示

这篇文章解释了they are implemented in SQL Server.他们没有在“幕后”做类似的事情。堆栈假脱机将删除行,因此无法知道后面的行是否与已删除的行重复。支持UNION 需要一种不同的方法。

与此同时,您可以很容易地在多语句 TVF 中实现相同的目标。

下面举个傻例子(Postgres Fiddle)

WITH R
     AS (SELECT 0 AS N
         UNION
         SELECT ( N + 1 )%10
         FROM   R)
SELECT N
FROM   R 

UNION 更改为UNION ALL 并在末尾添加DISTINCT 不会使您免于无限递归。

但您可以将其实现为

CREATE FUNCTION dbo.F ()
RETURNS @R TABLE(n INT PRIMARY KEY WITH (IGNORE_DUP_KEY = ON))
AS
  BEGIN
      INSERT INTO @R
      VALUES      (0); --anchor

      WHILE @@ROWCOUNT > 0
        BEGIN
            INSERT INTO @R
            SELECT ( N + 1 )%10
            FROM   @R
        END

      RETURN
  END

GO

SELECT *
FROM   dbo.F () 

以上使用IGNORE_DUP_KEY 丢弃重复项。如果列列表太宽而无法编入索引,则需要 DISTINCTNOT EXISTS 代替。您可能还需要一个参数来设置最大递归数并避免无限循环。

【讨论】:

  • ...所以这基本上是SQL Server的技术限制。有趣!
  • 附带说明,我真的不需要使用 UNION,很长一段时间以来我都觉得它违反直觉,昨天我终于好奇地研究了原因,只是为了发现似乎没有人讨论过它。
【解决方案3】:

pred 的一个很好的解释在这里发布猜测:https://sqlite.org/lang_with.html

优化说明:......运行上面的例子只需要很少的内存。但是,如果该示例使用 UNION 而不是 UNION ALL,则 SQLite 将不得不保留所有先前生成的内容以检查重复项。出于这个原因,程序员应该尽量使用 UNION ALL 而不是 UNION。

【讨论】:

    猜你喜欢
    • 2012-02-17
    • 2021-11-20
    • 1970-01-01
    • 1970-01-01
    • 2013-09-12
    • 2013-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多