【问题标题】:Can you detect INSERT-EXEC scenario's?你能检测到 INSERT-EXEC 场景吗?
【发布时间】:2026-01-25 23:35:02
【问题描述】:

是否可以检测当前存储过程是否被INSERT-EXEC 语句调用?

是的,我知道我们可能不想再使用INSERT-EXEC 语句...这不是我要问的问题。

我使用INSERT-EXEC 的原因是因为我希望促进存储过程的重用,而不是一直重写相同的 SQL。

这就是我关心的原因:
INSERT-EXEC 场景下,一旦请求ROLLBACK,原始错误消息就会丢失。因此,现在创建的任何记录都将成为孤立记录。

示例:

ALTER PROCEDURE [dbo].[spa_DoSomething]
(
    @SomeKey    INT,
    @CreatedBy  NVARCHAR(50)
)
AS
BEGIN
    SET NOCOUNT ON;

    BEGIN TRY
        BEGIN TRANSACTION

        -- SQL runs and throws an error of some kind.

        COMMIT TRAN
    END TRY
    BEGIN CATCH

        IF @@TRANCOUNT > 0
            ROLLBACK TRAN

        -- If this procedure is called using an INSERT-EXEC
        -- then the original error will be lost at this point because
        -- "Cannot use the ROLLBACK statement within an INSERT-EXEC statement." 
        -- will come-up instead of the original error.

        SET @ErrorMessage = ERROR_MESSAGE();
        SET @ErrorSeverity =    ERROR_SEVERITY();
        SET @ErrorState =       ERROR_STATE();

        RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState)
    END CATCH

    RETURN @@Error
END

【问题讨论】:

    标签: sql sql-server error-handling raiserror


    【解决方案1】:

    基于您不能嵌套 INSERT--EXECUTE... 语句的事实,我想出了一些杂物(好吧,这是一个大杂物)。基本上,如果您的“问题”过程是INSERT--EXECUTE 的目标,并且它本身包含INSERT--EXECUTE,则会引发错误。要完成这项工作,您必须在过程中调用(很可能毫无意义)INSERT--EXECUTE,并将其包装在具有适当处理的TRY--CATCH 块中。笨拙而迟钝,但如果没有其他问题,可能值得一试。

    使用以下内容进行测试。这将创建三个过程:

    IF objectproperty(object_id('dbo.Foo1'), 'isProcedure') = 1
        DROP PROCEDURE dbo.Foo1
    IF objectproperty(object_id('dbo.Foo2'), 'isProcedure') = 1
        DROP PROCEDURE dbo.Foo2
    IF objectproperty(object_id('dbo.Foo3'), 'isProcedure') = 1
        DROP PROCEDURE dbo.Foo3
    
    GO
    --  Returns a simple data set
    CREATE PROCEDURE Foo1
    
    AS
    
        SET NOCOUNT on
    
        SELECT name
         from sys.databases
    
    GO
    --  Calls Foo1, loads data into a local temp table, then returns those contents
    CREATE PROCEDURE Foo2
    
    AS
    
        SET NOCOUNT on
    
        CREATE TABLE #Temp (DBName sysname not null)
    
        BEGIN TRY
            INSERT #Temp (DBName)
             EXECUTE Foo1
        END TRY
    
        BEGIN CATCH
            IF ERROR_NUMBER() = 8164
                PRINT 'Nested INSERT EXECUTE'
            ELSE
                PRINT 'Unanticipated err: ' + cast(ERROR_NUMBER() as varchar(10))
        END CATCH
    
    
        SELECT *
         from #Temp
    
    GO
    --  Calls Foo2, loads data into a local temp table, then returns those contents
    CREATE PROCEDURE Foo3
    
    AS
    
        SET NOCOUNT on
    
        CREATE TABLE #Temp2 (DBName sysname not null)
    
        INSERT #Temp2 (DBName)
         EXECUTE Foo2
    
        SELECT *
         from #Temp2
    GO
    

    EXECUTE Foo1 将返回“基础”数据集。

    EXECUTE Foo2 将调用 Foo1,将数据加载到临时表中,然后返回该表的内容。

    EXECUTE Foo3 尝试做与 Foo2 相同的事情,但它调用 Foo2。这会导致嵌套的INSERT--EXECUTE 错误,由Foo2 的TRY--CATCH 检测和处理。

    【讨论】:

    • 在大多数情况下,这似乎不是一个实用的解决方案,但对于纯粹的独创性 +1 :)
    • 感谢您的意见...我稍后会看看这个。与此同时,我们并没有真正的 MULTI-NEST 场景(只有 1 个级别)。有没有更好的方法来促进存储过程的重用?
    • 我想出了这个组合是因为我不知道、想不到、甚至无法想象 SQL 提供了一种方法来检测给定的 EXECUTE 是否被称为插入语句。这确实假设您当前没有嵌套插入执行,因为如果您这样做了,您会抱怨这一点(我当然有!)
    • 如果事实证明无法检测到这一点,我建议您明确指出何时通过 INSERT 调用或不调用 proc。这就像添加和正确分配新参数一样简单(也很尴尬),例如@CalledByInsert bit。尴尬,因为您必须检查并可能修改大量代码。
    • 我想我真正的挣扎是“你如何正确地促进存储过程的代码重用”?如果我必须消除 INSERT-EXECS 并将 proc 的代码粘贴到每个家长电话中……那“似乎”更糟。你有什么想法吗?
    【解决方案2】:

    也许@@NESTLEVEL 可以提供帮助:

    http://msdn.microsoft.com/en-us/library/ms187371.aspx

    【讨论】: