Ошибки: «Оператор INSERT EXEC не может быть вложенным».и «Невозможно использовать инструкцию ROLLBACK в инструкции INSERT-EXEC».Как это решить? - PullRequest
85 голосов
/ 25 сентября 2010

У меня есть три хранимые процедуры Sp1, Sp2 и Sp3.

Первый (Sp1) выполнит второй (Sp2) и сохранит возвращенные данные в @tempTB1, а второй выполнит третий (Sp3) и сохранит данные в @tempTB2 .

Если я выполню Sp2, он будет работать, и он вернет мне все мои данные из Sp3, но проблема в Sp1, когда я выполню его, отобразится эта ошибка:

Оператор INSERT EXEC не может быть вложенным

Я попытался изменить место execute Sp2, и у меня появилась другая ошибка:

Невозможно использовать инструкцию ROLLBACK в инструкции INSERT-EXEC.

Ответы [ 10 ]

86 голосов
/ 26 сентября 2010

Это распространенная проблема при попытке «всплыть» данных из цепочки хранимых процедур.Ограничением в SQL Server является то, что вы можете одновременно использовать только один INSERT-EXEC.Я рекомендую посмотреть Как обмениваться данными между хранимыми процедурами , который является очень тщательной статьей о шаблонах, позволяющих обойти этот тип проблемы.

Например, можно обойти проблему, чтобы превратить Sp3 вТабличная функция.

17 голосов
/ 02 июня 2012

Это единственный «простой» способ сделать это в 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', И НЕ МОЖЕТЕ добавлять динамический sql к этому ни внутри вызова openrowset, ни для строки, содержащей параметры вашей хранимой процедуры, или для имени таблицы. Вот почему вы должны использовать временную таблицу, а не переменные таблицы, что было бы лучше, поскольку в большинстве случаев она выполняет временную таблицу.

8 голосов
/ 16 мая 2016

ОК, воодушевленный 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

*/
7 голосов
/ 04 мая 2016

Моя работа вокруг этой проблемы всегда заключалась в том, чтобы использовать принцип, согласно которому одиночные хеш-таблицы находятся в области действия любых вызываемых процедур.Итак, у меня есть опция switch в параметрах proc (по умолчанию установлено значение off).Если это включено, вызываемый proc будет вставлять результаты в временную таблицу, созданную в вызывающем proc.Я думаю, что в прошлом я сделал еще один шаг и поместил некоторый код в вызываемую процедуру, чтобы проверить, существует ли в области действия одна хеш-таблица, если она затем вставит код, иначе вернет набор результатов.Кажется, работает хорошо - лучший способ передачи больших наборов данных между процессами.

4 голосов
/ 19 августа 2014

Я нашел способ обойти это - преобразовать один из инструментов в табличную функцию.Я понимаю, что это не всегда возможно, и вводит свои ограничения.Однако мне всегда удавалось найти хотя бы одну из процедур, подходящих для этого.Мне нравится это решение, потому что оно не вводит никаких «взломов» в решение.

3 голосов
/ 22 ноября 2016

Этот трюк работает для меня.

У вас нет этой проблемы на удаленном сервере, потому что на удаленном сервере последняя команда вставки ожидает выполнения результата предыдущей команды.Это не относится к тому же серверу.

Воспользуйтесь этой ситуацией для обходного пути.

Если у вас есть право на создание Связанного сервера, сделайте это.Создайте тот же сервер, что и связанный сервер.

  • в SSMS, войдите на свой сервер
  • , перейдите в «Объект сервера»
  • Щелкните правой кнопкой мыши «Связанные серверы», затем«Новый связанный сервер»
  • в диалоговом окне укажите любое имя вашего связанного сервера: например: THISSERVER
  • тип сервера - «Другой источник данных»
  • Поставщик: Microsoft OLEПоставщик БД для SQL сервера
  • Источник данных: ваш IP, он также может быть просто точкой (.), Потому что это localhost
  • Перейдите на вкладку «Безопасность» и выберите третий.Сделайте, используя текущий контекст безопасности входа в систему "
  • Вы можете редактировать параметры сервера (3-я вкладка), если хотите
  • Нажмите OK, ваш связанный сервер создан

теперь ваша команда Sql в SP1

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

Поверьте, она работает даже при наличии динамической вставки в SP2

1 голос
/ 03 февраля 2017

как насчет просто сохранить вывод в статической таблице? Как

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

это не идеально, но это так просто, и вам не нужно все переписывать.

UPDATE : предыдущее решение не работает с параллельными запросами (асинхронный и многопользовательский доступ), поэтому теперь я использую временные таблицы

-- 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
1 голос
/ 14 апреля 2013

У меня была та же проблема и проблема с дублирующимся кодом в двух или более sprocs. Я закончил тем, что добавил дополнительный атрибут для «mode». Это позволяло существовать общему коду внутри одного sproc и режиму потока и результирующего набора sproc.

0 голосов
/ 19 мая 2017

Объявление выходной переменной курсора для внутреннего 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 голосов
/ 29 ноября 2016

В SQL Server 2008 R2 произошла ошибка в столбцах таблицы, которая вызвала ошибку отката.Он исчез, когда я исправил свою табличную переменную sqlcmd, заполненную оператором insert-exec, в соответствии с возвращенной хранимой процедурой.Отсутствовал 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"
...