我有三个存储过程Sp1
,Sp2
和Sp3
。
第一个(Sp1
)将执行第二个(Sp2
)并将返回的数据保存到@tempTB1
,第二个将执行第三个(Sp3
)并将数据保存到@tempTB2
。
如果我执行Sp2
它会工作,它将从Sp3
返回我的所有数据,但问题出在Sp1
,当我执行它时会显示这个错误:
INSERT EXEC语句不能嵌套
我尝试更改execute Sp2
的位置,它显示另一个错误:
无法使用ROLLBACK语句 在INSERT-EXEC语句中。
答案 0 :(得分:86)
尝试从存储过程链中“冒泡”数据时,这是一个常见问题。 SQL Server中的限制是一次只能激活一个INSERT-EXEC。我建议查看How to Share Data Between Stored Procedures这是一篇关于解决此类问题的模式的非常详尽的文章。
例如,解决方法可能是将Sp3转换为表值函数。
答案 1 :(得分:17)
这是在SQL Server中执行此操作的唯一“简单”方法,没有一些巨大的复杂创建函数或执行的sql字符串调用,这两者都是可怕的解决方案:
实施例
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,包含存储过程参数的字符串或表名。这就是为什么你必须使用临时表而不是表变量,因为它在大多数情况下都会执行临时表。
答案 2 :(得分:7)
好的,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
*/
答案 3 :(得分:6)
我对这个问题的解决一直是使用单个散列临时表在任何被调用的proc的范围内的原则。所以,我在proc参数中有一个选项开关(默认设置为off)。如果打开它,则被调用的proc会将结果插入到调用proc中创建的临时表中。我认为在过去我已经更进一步,并在被调用的proc中放入一些代码来检查作用域中是否存在单个哈希表,如果是,则插入代码,否则返回结果集。似乎运行良好 - 在procs之间传递大数据集的最佳方式。
答案 4 :(得分:4)
我发现解决方法是将其中一个产品转换为表值函数。我意识到这并不总是可行的,并且引入了它自己的局限性。但是,我总能找到至少一个程序是一个很好的候选人。我喜欢这个解决方案,因为它不会给解决方案带来任何“黑客攻击”。
答案 5 :(得分:3)
这个技巧对我有用。
您在远程服务器上没有此问题,因为在远程服务器上,最后一个insert命令等待执行上一个命令的结果。在同一台服务器上并非如此。
获得解决方案的利益。
如果您拥有创建链接服务器的正确权限,请执行此操作。 创建与链接服务器相同的服务器。
现在SP1中的Sql命令是
insert into @myTempTable
exec THISSERVER.MY_DATABASE_NAME.MY_SCHEMA.SP2
相信我,即使你在SP2中有动态插入它也能正常工作
答案 6 :(得分:1)
我遇到了同样的问题,担心两个或多个sprocs中的重复代码。我最终为“模式”添加了一个额外的属性。这允许公共代码存在于一个sproc内部以及sproc的模式定向流和结果集。
答案 7 :(得分: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
它不理想,但它如此简单,你不需要重写所有内容。
<强>更新强>: 以前的解决方案不适用于并行查询(异步和多用户访问),因此现在使用临时表
-- 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
答案 8 :(得分:1)
我建议阅读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 处理程序将触发,但事务已注定,即您必须将其回滚。最终效果是按要求实现了回滚,但触发回滚的原始错误消息丢失了。这可能看起来是一件小事,但它使故障排除变得更加困难,因为当您看到此错误时,您只知道出了点问题,但您不知道是什么。
答案 9 :(得分: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
已准备就绪。循环,然后关闭并解除分配。
答案 10 :(得分:0)
如果您能够使用其他相关技术(例如C#),建议您将内置的SQL命令与Transaction参数一起使用。
var sqlCommand = new SqlCommand(commandText, null, transaction);
我创建了一个简单的控制台应用程序,该应用程序演示了此功能,可以在这里找到: https://github.com/hecked12/SQL-Transaction-Using-C-Sharp
简而言之,C#允许您克服此限制,您可以检查每个存储过程的输出并随意使用该输出,例如,可以将其提供给另一个存储过程。如果输出正常,则可以提交事务,否则,可以使用回滚来还原更改。
答案 11 :(得分:-1)
在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"