递归公用表表达式如何工作?

时间:2016-07-27 05:37:51

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

要求是编写一个带有2个参数的表值函数。

@start_number  (a integer value)
@end_number (a integer value)

该函数将返回一个包含@start_number@end_number之间数字的表格,包括参数编号。

以下是代码:

Declare @start_number int
Declare @end_number int
Declare @max_recursion int

Set @start_number = 10
Set @end_number = 100
Set @max_recursion = (@end_number - @start_number)

Declare @numbers table(number int)   --start
;with numbers(number) as
(
 select @start_number as number
 union all
 select number + 1 from numbers where number between @start_number and @end_number - 1
)

insert into @numbers(number)
select number from numbers option(maxrecursion 10000)
select number from @numbers       --end

查询提供了所需的输出。但是,我需要从startend的解释,这些行是如何工作的?max recursion在这里的目的是什么?

1 个答案:

答案 0 :(得分:1)

-- table variable declaration
Declare @numbers table(number int)   --start

-- CTE declaration
;with numbers(number) as
( -- CTE body
 -- anchor 
 select @start_number as number -- [a]
 union all

 -- recursive call to itself
 select number + 1 from numbers -- [r]

 -- recursion limit
 where number between @start_number and @end_number - 1
)

insert into @numbers(number)

-- insert into @numbers table all values from CTE
select number from numbers option(maxrecursion 10000)

-- pretty obvious select
select number from @numbers       --end

表变量几乎与通常的表一样操作。 关于临时表和表变量之间的区别,请参考this questionmsdn

CTE就像嵌套查询,临时结果集。主要区别在于它可以自我引用(就像你的情况一样)。您只使用一列number声明CTE。如果可以解析列,则不需要手动指定列。这是一个recursive CTE - 选择一个数字并加入自己的数字附加+1。因此,每个后续行都有+1到前一行。当您从CTE中选择时,锚select @start_number as number被执行。比它与所有东西连接,返回形式本身(添加+1)。

让我们一步一步走进CTE:

on [a] return 1
on [r] add +1 to everything from self ([a] and [r]):
    on [a] return 1
    on [r] add +1 to everything from self ([a] and [r]):
        on [a] return 1
        on [r] add +1 to everything from self ([a] and [r]):
            on [a] return 1
            on [r] add +1 to everything from self ([a] and [r]):
                ...

所以你的结果集是(每个嵌套级别在{}内):

{1, {1, {1, {1, ...} +1 } +1 } +1 }

其中{1} +1 => {2}{1, {1} +1 } +1 => {1, 2} +1 => {2, 3}等等

因此,如果您不限制此查询,嵌套将是无限的。这就是recursion limit出现的原因。引擎将简单地过滤您的无限查询并停止嵌套,因为在任何后续选择中它将获得与您不匹配的值,并且该选择将返回空结果集。例如,您过滤的值小于10:

....
on [a] return 1 -- will result 8, match
on [r] add +1 to everything from self ([a] and [r]):
    on [a] return 1 -- will result in 9, match
    on [r] add +1 to everything from self ([a] and [r]): -- (STOP)
        on [a] return 1 -- will result in 10, not match
                        -- nothing else will be queried as on (STOP) empty result returned
                        -- so, recursion stopped

请注意,如果您在递归调用中过滤不正确,例如

where number between @start_number + 1 and @end_number - 1

第一次递归调用将返回给你@start_number,这与你的过滤器不匹配,并且不会发生递归。

实际上,过滤

where number between @start_number and @end_number - 1

过度。它可以简化为

where number < @end_number

注意,在这两种情况下,过滤器都不会与@end_number匹配,因为它会将+1添加到之前的值。因此,当您从@start_number(锚点)开始并与递归连接时,将返回

  { @start_number, ..., @end_number -1 } +1

结果将是

  { @start_number, @start_number +1, ..., @end_number -1 +1 }
--  ^^^^^^^^^^^^^ - anchor
--     recursion - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
select语句Hint中的

option(maxrecursion 10000)会覆盖默认的最大递归级别(默认情况下为100,有效值为[0..32767])。因此,您的特定功能限制为10000个值,如果您尝试生成超过10000个值,您将收到此错误:

  

声明终止。语句完成之前,最大递归10000已用尽。

请注意,限制递归的过滤器必须放在CTE中,而不是放在CTE的位置,因为在这种情况下引擎会继续迭代CTE寻找下一个匹配值,但是你的CTE实际上是无限的,会发生错误

其余代码非常简单 - 结果表格CTE插入@numbers表变量然后简单选择。

变量@max_recursion可以在未使用时删除(并且不能在OPTION子句中使用)。