2010-09-25 1 views
71

У меня есть три хранимых процедуры Sp1, Sp2 и Sp3.Ошибки: «Инструкция INSERT EXEC не может быть вложенной». и «Невозможно использовать инструкцию ROLLBACK в инструкции INSERT-EXEC». Как это решить?

Первый (Sp1) выполнит второй (Sp2) и сохранить полученные данные в @tempTB1, а второй будет выполнять третий (Sp3) и сохранить данные в @tempTB2.

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

INSERT EXEC statement cannot be nested

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

Cannot use the ROLLBACK statement within an INSERT-EXEC statement.

ответ

75

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

Например, работа вокруг могла бы превратить Sp3 в табличную функцию.

+0

неработающая ссылка ИЛИ не отвечающий сайт. – SouravA

15

Это единственный «простой» способ сделать это в 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, либо для строки, содержащей параметры хранимой процедуры, или для имени таблицы. Вот почему вы должны использовать таблицу temp, а не переменные таблицы, что было бы лучше, поскольку в большинстве случаев она выполняет временную таблицу.

+0

Не обязательно использовать SET FMTONLY OFF. Вы можете просто добавить IF (1 = 0), который возвращает пустую таблицу с теми же типами данных, которые процедура обычно возвращает. –

+0

Таблицы переменных таблицы и таблицы хранят свои данные по-разному. Табличные переменные предполагается использовать для небольших наборов результатов, поскольку оптимизатор запросов не поддерживает статистику переменных таблицы. Поэтому для больших наборов данных почти всегда лучше использовать таблицы Temp. Вот хорошая статья блога на нем http://www.mssqltips.com/sqlservertip/2825/sql-server-temp-table-vs-table-variable-performance-testing/ – gh9

+0

@ gh9 да, но это ужасно Идея для больших результирующих множеств в любом случае. Статистика и использование фактической таблицы в базе данных temp могут вызвать значительные накладные расходы. У меня есть процедура, которая возвращает набор записей с 1 строкой текущих значений (запрос нескольких таблиц) и процедуру, которая хранит это в переменной таблицы и сравнивает ее со значениями в другой таблице с тем же форматом. Переход от таблицы temp к переменной таблицы ускорил среднее время от 8 мс до 2 мс, что важно, когда он вызывается несколько раз в секунду в течение дня и 100 000 раз в ночном процессе. –

1

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

3

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

+0

, но недостатком является проблема с обработкой исключений, если функция сложна, правильно? – Muflix

3

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

+0

Мне нравится этот ответ, и я бы держал пари, что вы получите больше голосов, если будете предлагать и пример. – jimhark

3

OK, ободренный 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 

*/ 
2

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

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

Прибыль этой ситуации для обходного пути.

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

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

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

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

Поверь, это работает даже у вас есть динамические вставки в SP2

-1

В SQL Server 2008 R2 у меня было несоответствие в столбцах таблицы, которые вызвали ошибку Rollback. Он ушел, когда я исправил переменную таблицы sqlcmd, заполненную оператором insert-exec, чтобы соответствовать возврату хранимой процедуры. Отсутствовал org_code. В файле cmd windows он загружает результат хранимой процедуры и выбирает ее.

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" 
+0

ОП задавал вопрос об ошибке, возникающей при использовании операторов insert-exec во вложенных хранимых процедурах. Ваша проблема будет возвращать другую ошибку, например «Список выбора для оператора INSERT содержит меньше элементов, чем список вставки». Число значений SELECT должно соответствовать количеству столбцов INSERT ». – Losbear

+0

Это скорее предупреждение о том, что это сообщение можно получить ошибочно. – user3448451

0

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

-- 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: предыдущее решение не очень хорошо работает с параллельными запросами (асинхронным и многопользовательским ACCESSING) поэтому сейчас И с помощью временных таблиц

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

Как правило, вы не можете создавать рекламные ролики SProc, как вы можете, с помощью таблиц. Вам нужно будет расширить свой пример с помощью дополнительных ссылок, так как этот подход не очень легко известен или принят. Кроме того, это похоже на выражение Lambda Expression больше, чем выполнение SProc, которое ANSI-SQL не допускает подходов Lambda Expression. – GoldBishop

+0

Это работает, но я обнаружил, что он не работает хорошо с параллельными запросами (асинхронный и многопользовательский доступ). Поэтому теперь я использую метод temp table. Я обновил свой ответ. – Muflix

+1

Логика Temp table хороша, это была ссылка SProc, с которой я был связан. Sproc не может по сути быть запрошен напрямую. Функции с табличным значением могут быть напрямую запрошены.Должен, как вы упомянули в своей обновленной логике, лучший подход - это Temp Table, session, instance или global и работать с этой точки. – GoldBishop

0

Объявить переменную курсора вывода во внутренний sp:

@c CURSOR VARYING OUTPUT 

Затем объявите курсор c для выбора, который вы хотите вернуть. Затем откройте курсор. Затем установите ссылку:

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

Не закрывать или перераспределить.

Теперь называют внутренний зр от внешней, снабжающего параметра курсора, как:

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

После того, как внутренняя зр выполняет, ваш @cOUT готов принести. Loop, а затем закрыть и освободить.

 Смежные вопросы

  • Нет связанных вопросов^_^