为什么Transact-SQL中的递归CTE需要UNION ALL而不是UNION?

时间:2017-12-27 21:26:14

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

我认为锚是必要的,这是有道理的。而且我知道需要$input = Storage::disk('s3')->getDriver(); $output = Storage::disk('ftp')->getDriver(); $output->writeStream($ftp_file_path, $input->readStream($s3_file_path)); ,如果您的递归CTE没有,它只是不起作用......但我无法找到一个很好的解释为什么这样做是这样的。所有文档都说明你需要它。

为什么无法我们在递归查询中使用UNION ALL而不是UNION?在更深层次的递归中不包含重复项似乎是个好主意,不是吗?我认为,这样的事情应该已经在幕后工作了。

3 个答案:

答案 0 :(得分:6)

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

如果您对此功能有很强的理由,可以在Connect(或其替换的URL)提供反馈。

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

SQL Standards中有很多地方使用代码展示UNION,例如下面

enter image description here

本文解释了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。您可能还需要一个参数来设置最大递归次数并避免无限循环。

答案 1 :(得分:2)

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

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

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

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

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

答案 2 :(得分:1)

pred帖子推测的一个很好的解释:https://sqlite.org/lang_with.html

<块引用>

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