是什么导致了这种奇怪的SQL行为?

时间:2011-07-07 18:25:24

标签: sql sql-server sql-server-2005 stored-procedures recursion

运行 SQL 2005 X64。

首先,在数据库上创建以下存储过程:

CREATE PROCEDURE dbo.Test 
    @Value int = null

AS

BEGIN

    IF (IsNull(@Value, '') = '')
        SELECT '*I am NULL!*'
    ELSE
        SELECT 'I am ' + CONVERT(varchar(20), @Value)

END

尝试按如下方式执行上述过程,然后得到以下结果:

EXEC dbo.Test

我是NULL!

现在,改变proc以使EXEC语句成为sproc本身的一部分:

ALTER PROCEDURE dbo.Test 
    @Value int = null

AS

BEGIN

    IF (IsNull(@Value, '') = '')
        SELECT 'I am NULL!'
    ELSE
        SELECT 'I am ' + CONVERT(varchar(20), @Value)

END

EXEC dbo.Test

如果你现在执行它,你会得到......

我是NULL!

我是NULL!

我是NULL!

... ad infinitum 直到输出因此错误而中断:

  

Msg 217,Level 16,State 1,Procedure   测试,第16行最大存储量   程序,功能,触发器或视图   超出嵌套级别(限制32)。

暂时忽略这根本不是标准做法,并且很可能有人会偶然做出类似这样的事情,有人可以提供一些关于SQL 2005“思考”的低级别见解。执行这个过程的第二个化身?

5 个答案:

答案 0 :(得分:6)

您的代码按预期运行。该程序自称为recursively

如果想要,请尝试以下操作:

ALTER PROCEDURE dbo.Test 
    @Value int = null

AS

BEGIN

    IF (IsNull(@Value, '') = '')
        SELECT 'I am NULL!'
    ELSE
        SELECT 'I am ' + CONVERT(varchar(20), @Value)

END

GO

EXEC dbo.Test

如果确实希望使用递归,则必须定义base case(AKA“退出条件”),这将使存储过程退出递归堆栈。

答案 1 :(得分:6)

递归是因为一切都被认为是proc的一部分,而不仅仅是BEGIN到END块。

来自我的评论:

没什么大不了的。它将处理所有事情,直到下一个GO或批次结束的其他指标作为proc的一部分。作为过程的一部分,最外面的BEGIN和END不是必需的语法。

答案 2 :(得分:1)

正如其他人所提到的那样,它被称为递归。

您可以避免使用@Adrian显示(使用'GO'来阻止sp调用自身),或者您也可以使用控制结构将其转义...

如果您想了解递归,可以学习以下示例/实验:http://msdn.microsoft.com/en-us/library/aa175801.aspx

答案 3 :(得分:1)

它允许32个嵌套调用。每次执行Exec时,您都会永远嵌套它。所以递归思考。

执行程序    选择   EXEC     选择      EXEC        无限。

一旦达到第32次嵌套调用,就会达到最大值,然后说我无法继续。

答案 4 :(得分:0)

我对这个问题的解读并不是“为什么我的SP会出现递归?”但是“为什么递归限制在32以及如何绕过它?”

我完全忘记了SQL Recursion会像你那样死在你身上。

我刚刚解决的答案是使用TRY-CATCH和@@ NestLevel。以下是一个小型示范装置。在你的代码中,拥有一个独立的结束条件会好得多,例如用完要处理的块。

我的代码被编辑器破坏了,我没有时间处理你的问题。

BEGIN TRY DROP PROCEDURE dbo.Nester END TRY BEGIN CATCH END catch

GO CREATE PROCEDURE dbo.Nester @NestLevel INT = 0 OUT 如 开始     DECLARE @MaxActNestLevel INT = 40;

SELECT @NestLevel += 1;

PRINT (CONVERT(sysname, @@NestLevel) + '    ' + CONVERT(sysname, @NestLevel))

IF @NestLevel < @MaxActNestLevel
BEGIN TRY
    EXEC dbo.Nester @NestLevel OUT
END TRY
BEGIN CATCH
    PRINT 'Catch Block'
    PRINT (ERROR_NUMBER())

    SELECT @NestLevel += 1;

    IF @@NestLevel < 30 --AND ERROR_NUMBER() = 217
    BEGIN
        EXEC dbo.Nester @NestLevel OUT
    END
    ELSE 
        THROW

END CATCH

END 走 EXEC dbo.Nester;

相关问题