错误:“INSERT EXEC 语句不能嵌套。”和“不能在 INSERT-EXEC 语句中使用 ROLLBACK 语句。”如何解决这个问题?

Posted

技术标签:

【中文标题】错误:“INSERT EXEC 语句不能嵌套。”和“不能在 INSERT-EXEC 语句中使用 ROLLBACK 语句。”如何解决这个问题?【英文标题】:Errors: "INSERT EXEC statement cannot be nested." and "Cannot use the ROLLBACK statement within an INSERT-EXEC statement." How to solve this? 【发布时间】:2011-04-17 06:04:09 【问题描述】:

我有三个存储过程Sp1Sp2Sp3

第一个(Sp1)将执行第二个(Sp2)并将返回的数据保存到@tempTB1,第二个将执行第三个(Sp3)并将数据保存到@tempTB2 .

如果我执行Sp2,它会工作,它会从Sp3返回我所有的数据,但问题出在Sp1,当我执行它时会显示这个错误:

INSERT EXEC 语句不能嵌套

我试图更改execute Sp2 的位置,但它显示另一个错误:

不能使用 ROLLBACK 语句 在 INSERT-EXEC 语句中。

【问题讨论】:

【参考方案1】:

当尝试从存储过程链中“冒泡”数据时,这是一个常见问题。 SQL Server 中的一个限制是一次只能激活一个 INSERT-EXEC。我建议查看How to Share Data Between Stored Procedures,这是一篇关于解决此类问题的模式的非常详尽的文章。

例如,可以将 Sp3 转换为表值函数。

【讨论】:

链接断开或网站无响应。 您知道不允许这样做的技术原因是什么吗?我找不到这方面的任何信息。 不幸的是,这通常不是一个选项。许多类型的重要信息只能可靠地从系统存储过程获得(因为在某些情况下,各自的管理视图包含不可靠/过时的数据;例如@返回的信息987654322@).【参考方案2】:

这是在 SQL Server 中执行此操作的唯一“简单”方法,无需一些巨大的复杂创建函数或执行的 sql 字符串调用,这两个都是糟糕的解决方案:

    创建临时表 openrowset 将您的存储过程数据放入其中

示例:

INSERT INTO #YOUR_TEMP_TABLE
SELECT * FROM OPENROWSET ('SQLOLEDB','Server=(local);TRUSTED_CONNECTION=YES;','set fmtonly off EXEC [ServerName].dbo.[StoredProcedureName] 1,2,3')

注意:您必须使用 'set fmtonly off',并且不能在 openrowset 调用中为此添加动态 sql,无论是包含存储过程参数的字符串还是表名。这就是为什么您必须使用临时表而不是表变量的原因,这会更好,因为它在大多数情况下都无法执行临时表。

【讨论】:

不必使用 SET FMTONLY OFF。您可以只添加一个 IF(1=0),它返回一个空表,该表具有与该过程通常返回的相同数据类型。 临时表和表变量以不同的方式存储它们的数据。表变量应该用于小型结果集,因为查询优化器不维护表变量的统计信息。因此,对于大型数据集,使用临时表几乎总是更好。这是一篇不错的博客文章mssqltips.com/sqlservertip/2825/… @gh9 是的,但这对于大型结果集来说是一个可怕的想法。临时数据库中实际表的统计和使用可能会导致大量开销。我有一个过程,它返回一个包含 1 行当前值(查询多个表)的记录集,以及一个将其存储在表变量中并将其与另一个具有相同格式的表中的值进行比较的过程。从临时表更改为表变量可将平均时间从 8 毫秒提高到 2 毫秒,这对于在一天中每秒调用多次以及在夜间进程中调用 100,000 次时非常重要。 为什么要在表变量上维护统计信息?重点是在 RAM 中创建一个临时表,该表将在查询完成后被销毁。根据定义,在此类表上创建的任何统计信息都不会被使用。通常,表变量中的数据尽可能保留在 RAM 中这一事实使它们在任何情况下都比临时表快服务器,几乎总是) 这不适用于扩展存储过程。错误是无法确定元数据,因为过程中的语句 'EXECUTE @retval OUTPUT' 调用了扩展存储过程【参考方案3】:

我对两个或多个存储过程中的重复代码有同样的问题和担忧。我最终为“模式”添加了一个附加属性。这允许公共代码存在于一个存储过程中以及存储过程的模式导向流和结果集内。

【讨论】:

【参考方案4】:

我发现一种解决方法是将其中一个 prods 转换为表值函数。我意识到这并不总是可能的,并引入了它自己的局限性。但是,我总能找到至少一个适合此的程序。我喜欢这个解决方案,因为它不会在解决方案中引入任何“黑客”。

【讨论】:

但是如果函数很复杂,一个缺点是异常处理的问题,对吧?【参考方案5】:

我解决这个问题的方法一直是使用单个哈希临时表在任何调用的 procs 范围内的原则。所以,我在 proc 参数中有一个选项开关(默认设置为关闭)。如果打开此选项,被调用的 proc 会将结果插入到调用 proc 中创建的临时表中。我认为过去我更进一步,在被调用的 proc 中放置了一些代码来检查单个哈希表是否存在于范围内,如果存在则插入代码,否则返回结果集。似乎运作良好 - 在 procs 之间传递大型数据集的最佳方式。

【讨论】:

我喜欢这个答案,我敢打赌,如果您提供示例,您将获得更多支持。 我已经这样做了很多年了。但在 SQL Azure 中仍然需要它吗? 是的,Azure 使用与 SQL Server 完全相同的引擎,唯一真正的区别是存储和 CPU 假设您有一个 SQLServer 托管实例。我发现 Azure SQL Server 完全不同。例如。跨数据库查询至少需要一个外部数据源。但是自从我上面的评论使用描述的解决方法没有任何问题以来,我已经编写了很多代码。【参考方案6】:

好的,在 jimhark 的鼓励下,这里有一个旧的单一哈希表方法的例子:-

CREATE PROCEDURE SP3 as

BEGIN

    SELECT 1, 'Data1'
    UNION ALL
    SELECT 2, 'Data2'

END
go


CREATE PROCEDURE SP2 as

BEGIN

    if exists (select  * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
        INSERT INTO #tmp1
        EXEC SP3
    else
        EXEC SP3

END
go

CREATE PROCEDURE SP1 as

BEGIN

    EXEC SP2

END
GO


/*
--I want some data back from SP3

-- Just run the SP1

EXEC SP1
*/


/*
--I want some data back from SP3 into a table to do something useful
--Try run this - get an error - can't nest Execs

if exists (select  * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
    DROP TABLE #tmp1

CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))

INSERT INTO #tmp1
EXEC SP1


*/

/*
--I want some data back from SP3 into a table to do something useful
--However, if we run this single hash temp table it is in scope anyway so
--no need for the exec insert

if exists (select  * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
    DROP TABLE #tmp1

CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))

EXEC SP1

SELECT * FROM #tmp1

*/

【讨论】:

我也使用了这个解决方法。谢谢你的想法! 很棒的解决方法。这帮助我了解了有关临时表范围的更多信息。 EG,我没有意识到你可以在 dynsql 字符串中使用临时表,如果它是在它之外声明的。类似的概念在这里。非常感谢。【参考方案7】:

这个技巧对我有用。

你在远程服务器上没有这个问题,因为在远程服务器上,最后一个插入命令等待上一个命令的结果执行。在同一台服务器上不是这样。

利用这种情况寻找解决方法。

如果您有创建链接服务器的正确权限,请执行此操作。 创建与链接服务器相同的服务器。

在 SSMS 中,登录到您的服务器 转到“服务器对象” 右键单击“链接服务器”,然后单击“新建链接服务器” 在对话框中,提供链接服务器的任何名称:例如:THISSERVER 服务器类型是“其他数据源” 提供程序:用于 SQL 服务器的 Microsoft OLE DB 提供程序 数据来源:你的IP,也可以只是一个点(.),因为它是localhost 转到“安全”选项卡并选择第三个“使用登录名的当前安全上下文进行” 您可以根据需要编辑服务器选项(第 3 个选项卡) 按 OK,您的链接服务器已创建

现在您在 SP1 中的 Sql 命令是

insert into @myTempTable
exec THISSERVER.MY_DATABASE_NAME.MY_SCHEMA.SP2

相信我,即使您在 SP2 中有动态插入,它也能正常工作

【讨论】:

这是一个 hack,但根据您的观点,接受/最多投票的答案也是如此。目前,我只需要能够在 ADO.NET 中执行我的 SP,但我为任何可能想要在 DB 层中使用 SP 的人注意了这个解决方法。 返回错误:服务器之间的远程调用中不允许使用表值参数。【参考方案8】:

在 SQL Server 2008 R2 上,我的表列不匹配导致回滚错误。当我修复了由 insert-exec 语句填充的 sqlcmd 表变量以匹配存储过程返回的变量时,它就消失了。它缺少 org_code。在windows cmd文件中,加载存储过程的结果并选择它。

set SQLTXT= declare @resets as table (org_id nvarchar(9), org_code char(4), ^
tin(char9), old_strt_dt char(10), strt_dt char(10)); ^
insert @resets exec rsp_reset; ^
select * from @resets;

sqlcmd -U user -P pass -d database -S server -Q "%SQLTXT%" -o "OrgReport.txt"

【讨论】:

OP 询问在嵌套存储过程中使用 insert-exec 语句时发生的错误。您的问题将返回不同的错误,例如“INSERT 语句的选择列表包含的项目少于插入列表。SELECT 值的数量必须与 INSERT 列的数量匹配。” 这更像是一个警告,可能会错误地获取此消息。【参考方案9】:

将输出存储到静态表怎么样?喜欢

-- SubProcedure: subProcedureName
---------------------------------
-- Save the value
DELETE lastValue_subProcedureName
INSERT INTO lastValue_subProcedureName (Value)
SELECT @Value
-- Return the value
SELECT @Value

-- Procedure
--------------------------------------------
-- get last value of subProcedureName
SELECT Value FROM lastValue_subProcedureName

它并不理想,但它非常简单,您不需要重写所有内容。

更新: 以前的解决方案不适用于并行查询(异步和多用户访问),因此现在我使用临时表

-- A local temporary table created in a stored procedure is dropped automatically when the stored procedure is finished. 
-- The table can be referenced by any nested stored procedures executed by the stored procedure that created the table. 
-- The table cannot be referenced by the process that called the stored procedure that created the table.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NULL
CREATE TABLE #lastValue_spGetData (Value INT)

-- trigger stored procedure with special silent parameter
EXEC dbo.spGetData 1 --silent mode parameter

嵌套spGetData存储过程内容

-- Save the output if temporary table exists.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NOT NULL
BEGIN
    DELETE #lastValue_spGetData
    INSERT INTO #lastValue_spGetData(Value)
    SELECT Col1 FROM dbo.Table1
END

 -- stored procedure return
 IF @silentMode = 0
 SELECT Col1 FROM dbo.Table1

【讨论】:

一般来说,您不能像使用 Tables 那样创建 SProc ad-hoc。您将需要通过更多参考来扩展您的示例,因为这种方法并不是很容易知道或接受的。此外,它更类似于 Lambda 表达式而不是 SProc 执行,ANSI-SQL 不允许 Lambda 表达式方法。 它可以工作,但我发现它也不适用于并行查询(异步和多用户访问)。因此,现在我正在使用临时表方法。我更新了我的答案。 Temp 表逻辑很好,是我关心的 SProc 引用。 Sproc 本身不能直接查询。可以直接从中查询表值函数。必须像您在更新的逻辑中提到的那样,最好的方法是临时表、会话、实例或全局,并从该点开始操作。【参考方案10】:

向内部sp声明一个输出游标变量:

@c CURSOR VARYING OUTPUT

然后将光标 c 声明为要返回的选择。 然后打开光标。 然后设置参考:

DECLARE c CURSOR LOCAL FAST_FORWARD READ_ONLY FOR 
SELECT ...
OPEN c
SET @c = c 

不要关闭或重新分配。

现在从提供游标参数的外部调用内部 sp:

exec sp_abc a,b,c,, @cOUT OUTPUT

一旦内部 sp 执行,您的 @cOUT 就可以获取了。循环然后关闭并释放。

【讨论】:

【参考方案11】:

如果您能够使用其他相关技术,例如 C#,我建议使用带有 Transaction 参数的内置 SQL 命令。

var sqlCommand = new SqlCommand(commandText, null, transaction);

我创建了一个简单的控制台应用程序来演示此功能,可以在此处找到: https://github.com/hecked12/SQL-Transaction-Using-C-Sharp

简而言之,C# 允许您克服此限制,您可以检查每个存储过程的输出并根据需要使用该输出,例如,您可以将其提供给另一个存储过程。如果输出正常,您可以提交事务,否则,您可以使用回滚恢复更改。

【讨论】:

【参考方案12】:

我在尝试将 Stored Proc 的结果导入临时表时遇到此问题,并且 Stored Proc 作为其自身操作的一部分插入到临时表中。问题是 SQL Server 不允许同一个进程同时写入两个不同的临时表。

接受的 OPENROWSET 答案工作正常,但我需要避免在我的流程中使用任何动态 SQL 或外部 OLE 提供程序,所以我选择了不同的路线。

我发现一个简单的解决方法是将存储过程中的临时表更改为表变量。它的工作原理与使用临时表完全相同,但不再与我的其他临时表插入冲突。

只是为了阻止评论,我知道你们中的一些人将要写,警告我不要将表变量作为性能杀手......我只能对你说的是,在 2020 年它会带来红利不 害怕表变量。如果这是 2008 年,并且我的数据库托管在具有 16GB RAM 并运行 5400RPM HDD 的服务器上,我可能会同意你的看法。但现在是 2020 年,我有一个 SSD 阵列作为我的主要存储设备和 数百 GB 的 RAM。我可以将整个公司的数据库加载到一个表变量中,并且仍然有足够的 RAM 可用。

表变量重新出现在菜单上!

【讨论】:

【参考方案13】:

我推荐阅读this整篇文章。以下是该文章中解决您的问题的最相关部分:

回滚和错误处理很困难

在我关于 SQL Server 中的错误和事务处理的文章中,我建议您应该始终使用类似的错误处理程序

BEGIN CATCH
   IF @@trancount > 0 ROLLBACK TRANSACTION
   EXEC error_handler_sp
   RETURN 55555
END CATCH

这个想法是,即使您没有在程序中启动交易,您也应该始终包含 ROLLBACK,因为如果您无法履行合同,则交易无效。

很遗憾,这不适用于 INSERT-EXEC。如果被调用的过程执行 ROLLBACK 语句,就会发生这种情况:

Msg 3915, Level 16, State 0, Procedure SalesByStore, Line 9 Cannot use the ROLLBACK statement within an INSERT-EXEC statement.

存储过程的执行被中止。如果任何地方都没有 CATCH 处理程序,则整个批处理被中止,事务被回滚。如果 INSERT-EXEC 在 TRY-CATCH 内,则 CATCH 处理程序将触发,但该事务注定要失败,也就是说,您必须回滚它。最终效果是按请求实现了回滚,但触发回滚的原始错误消息丢失了。这似乎是一件小事,但它使故障排除变得更加困难,因为当您看到此错误时,您只知道出了点问题,但您不知道是什么。

【讨论】:

这个问题的答案如何? 它直接提供了为什么 Insert-exec 在每种情况下都不起作用的信息,尤其是在冒泡错误和处理回滚时。现在,OP 已经标记了一个答案,但我的答案有助于提供额外的具体信息以及与此事极为相关的来源。 可能是也可能不是这个问题的答案,但它有助于我解决我遇到第一个错误消息的问题。像链接建议的那样将它移动到 UDF 并且它有效。

以上是关于错误:“INSERT EXEC 语句不能嵌套。”和“不能在 INSERT-EXEC 语句中使用 ROLLBACK 语句。”如何解决这个问题?的主要内容,如果未能解决你的问题,请参考以下文章

区分图像错误和表格错误

我收到错误错误类型参数一元减号和预期';'在 ':' 标记之前

python异常和错误的区别

错误处理和时间函数

Python错误和异常

400错误和404错误