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


98

我有三个存储过程Sp1Sp2Sp3

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

如果我执行,Sp2它将正常工作,它将从中返回我的所有数据Sp3,但是问题出在Sp1,当我执行它时,它将显示此错误:

INSERT EXEC语句不能嵌套

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

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

Answers:


100

尝试从存储过程链中“冒泡”数据时,这是一个常见问题。SQL Server中的一个限制是您一次只能激活一个INSERT-EXEC。我建议阅读“ 如何在存储过程之间共享数据”,这是一篇非常详尽的文章,介绍了解决此类问题的模式。

例如,一种变通方法是将Sp3转换为表值函数。


1
链接断开或网站无响应。
SouravA

6
您知道不允许使用什么技术原因吗?我找不到任何相关信息。
jtate

1
不幸的是,这通常不是一种选择。许多类型的重要信息只能从系统存储过程中可靠地获得(因为在某些情况下,相应的管理视图包含不可靠/过时的数据;示例是由返回的信息)。sp_help_jobactivity
GSerg

21

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

  1. 创建一个临时表
  2. 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),该IF返回一个空表,该空表的数据类型与该过程通常返回的数据类型相同。
GuillermoGutiérrez2013年

1
临时表和表变量存储数据的方式不同。由于查询优化器不维护表变量的统计信息,因此表变量应用于较小的结果集。因此,对于大型数据集,使用Temp表几乎总是更好。这是一篇不错的博客文章,mssqltips.com
sqlservertip /

@ gh9是的,但这对于大结果集还是一个可怕的想法。临时数据库中的统计信息和实际表的使用可能会导致大量开销。我有一个过程返回一个带有1行当前值的记录集(查询多个表),以及一个将其存储在表变量中并将其与另一个具有相同格式的表中的值进行比较的过程。从临时表更改为表变量可以将平均时间从8ms加快到2ms,这对于全天每秒调用几次,在夜间过程中调用100,000次非常重要。
杰森·古玛

为什么要在表变量上维护统计信息?重点是在RAM中创建一个临时表,查询完成后该表将被销毁。根据定义,永远不会使用在此类表上创建的任何统计信息。通常,在任何情况下,如果您的数据小于SQL Server可用的RAM量(在当前的SQL中有100GB +的内存池),那么表变量中的数据会尽可能保留在RAM中的事实使其比Temp Tables更快。服务器,几乎总是这样)
Geoff Griswald

但是,这不适用于扩展的存储过程。错误是无法确定元数据,因为语句'EXECUTE <procedurename> @retval OUTPUT in procedure ...'调用了扩展的存储过程
GSerg

11

好的,在吉姆哈克(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

*/

我也使用了这种解决方法。谢谢你的主意!
SQL_Guy

神奇的解决方法。这有助于我了解有关临时表作用域的更多信息。EG,我没有意识到,如果在dynsql字符串中声明了临时表,则可以在其中使用临时表。类似的概念在这里。非常感谢。
jbd

9

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


1
我喜欢这个答案,我敢打赌,如果您提供示例,您将获得更多投票。
jimhark's

我已经做了多年了。但是在SQL Azure中仍然有必要吗?
尼克·艾伦,

6

这个技巧对我有用。

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

解决这种情况,从中获利。

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

  • 在SSMS中,登录到您的服务器
  • 转到“服务器对象
  • 右键单击“链接服务器”,然后单击“新链接服务器”
  • 在对话框中,输入链接服务器的任何名称:例如:THISSERVER
  • 服务器类型为“其他数据源”
  • 提供程序:SQL Server的Microsoft OLE DB提供程序
  • 数据源:您的IP,也可以是点(。),因为它是localhost
  • 转到选项卡“安全性”,然后选择第三个选项“由登录名的当前安全性上下文制成”
  • 您可以根据需要编辑服务器选项(第三个选项卡)
  • 按确定,您的链接服务器已创建

现在您在SP1中的Sql命令是

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

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


4

我发现一种解决方法是将其中一个产品转换为表值函数。我意识到这并不总是可能的,并介绍了其自身的局限性。但是,我始终能够找到至少一个程序来解决此问题。我喜欢这个解决方案,因为它不会给解决方案带来任何“麻烦”。


但是缺点是如果函数很复杂,异常处理就会出现问题,对吗?
Muflix '17

2

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

接受的OPENROWSET答案工作正常,但是我需要避免在过程中使用任何Dynamic SQL或外部OLE提供程序,因此我采取了另一种方法。

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

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

表变量又回到了菜单上!


1

我有相同的问题,并且担心两个或多个存储过程中的重复代码。我最终为“模式”添加了一个附加属性。这样一来,通用代码就可以存在于一个sproc内,并且有模式定向的流程和该sproc的结果集。


1

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

-- 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

它并不理想,但是它是如此简单,您无需重写所有内容。

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

-- 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。您将需要在示例中添加更多参考资料,因为这种方法并不十分容易为人所知或接受。此外,它比Lambda表达式更像SProc执行,ANSI-SQL不允许使用Lambda Expression方法。
GoldBishop

它可以工作,但是我发现它也不适合并行查询(异步和多用户访问)。因此,现在Iam使用临时表方法。我更新了答案。
Muflix

1
Temp表逻辑很好,这是我关心的SProc参考。本质上,不能直接从Sproc中查询Sproc。可以直接查询表值函数。必须像在更新的逻辑中提到的那样,最好的方法是临时表,会话,实例或全局,并从那时开始操作。
GoldBishop's

0

向内部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就可以获取了。循环,然后关闭并取消分配。


0

如果您能够使用其他关联技术(例如C#),建议您将内置SQL命令与Transaction参数一起使用。

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

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

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


-1

在SQL Server 2008 R2上,表列不匹配,导致回滚错误。当我修复由insert-exec语句填充的sqlcmd表变量以匹配存储的proc返回的变量时,它消失了。它缺少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列的数量匹配。”
Losbear

这更多的是警告,有可能错误地获得此消息。
user3448451
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.