Помилки: "Виписка INSERT EXEC не може бути вкладена." та "Неможливо використовувати оператор ROLLBACK в операторі INSERT-EXEC." Як це вирішити?


98

У мене є три збережені процедури Sp1, Sp2і Sp3.

Перший ( Sp1) виконає другий ( Sp2) і збереже повернені дані у, @tempTB1а другий виконає третій ( Sp3) і збереже дані у @tempTB2.

Якщо я виконую це, Sp2він буде працювати, і він поверне мені всі мої дані з Sp3, але проблема полягає в тому Sp1, що коли я його виконую, він відобразить цю помилку:

Оператор INSERT EXEC не може бути вкладений

Я спробував змінити місце, execute Sp2і це відобразило мені ще одну помилку:

Неможливо використовувати оператор ROLLBACK в операторі INSERT-EXEC.

Відповіді:


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

Примітка. ВИ ОБОВ'ЯЗКОВО використовувати "вимкнути fmtonly off", і ви НЕ МОЖЕТЕ додати до цього динамічний sql або всередині виклику openrowset, або для рядка, що містить збережені параметри процедури, або для імені таблиці. Ось чому вам доведеться використовувати таблицю temp, а не змінні таблиці, що було б краще, оскільки вона виконує таблицю temp у більшості випадків.


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

1
Типові таблиці та змінні таблиці зберігають свої дані по-різному. Змінні таблиці можна використовувати для невеликих наборів результатів, оскільки оптимізатор запитів не підтримує статистику змінних таблиць. Тому для великих наборів даних майже завжди краще використовувати таблиці Temp. Ось приємна стаття в блозі про це mssqltips.com/sqlservertip/2825/…
gh9

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

Чому ви хочете, щоб статистика велася за змінною таблиці? Вся справа в тому, щоб створити тимчасову таблицю в оперативній пам'яті, яка буде знищена після завершення запиту. За визначенням будь-яка статистика, створена на такій таблиці, ніколи не використовувалася б. Як правило, той факт, що дані в змінній таблиці залишаються в оперативній пам’яті, де це можливо, робить їх швидшими, ніж Temp Tables в будь-якому сценарії, коли ваші дані менше, ніж об’єм оперативної пам’яті, доступний для SQL Server (що в ці дні становить 100 ГБ + пулів пам’яті для нашого SQL Сервери, майже завжди)
Джефф Грісвальд

Це не працює для розширених збережених процедур. Помилка метаданих не вдалося визначити, оскільки оператор "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, я не розумів, що ви можете використовувати таблицю temp у рядку dynsql, якщо вона була оголошена поза нею. Подібна концепція тут. Дуже дякую.
jbd

9

Моя робота над цією проблемою завжди полягала в тому, щоб використовувати принцип, згідно з яким окремі хеш-темп-таблиці знаходяться в межах будь-яких названих програм. Отже, у мене є перемикач параметрів у параметрах proc (стандартний параметр встановлено на вимкнено). Якщо це увімкнено, виклик proc вставить результати в таблицю темп, створену в виклику proc. Я думаю, що раніше я зробив це на крок далі і поставив якийсь код у виклику proc, щоб перевірити, чи існує одна хеш-таблиця в області застосування, якщо вона потім вставить код, інакше повернути набір результатів. Здається, працює добре - найкращий спосіб передачі великих наборів даних між документами.


1
Мені подобається ця відповідь, і я б покладався на сумніви, що ви отримаєте більше голосів, якщо б ви подали приклад та приклад.
jimhark

Я цим займаюся роками. Чи все-таки це потрібно в SQL Azure?
Нік Аллан

6

Цей трюк працює для мене.

У вас немає проблеми на віддаленому сервері, оскільки на віддаленому сервері команда останньої вставки очікує виконання попередньої команди. Це не так на одному сервері.

Прибуток у цій ситуації для вирішення проблеми.

Якщо у вас є правильний дозвіл на створення пов'язаного сервера, зробіть це. Створіть той самий сервер, що і пов'язаний сервер.

  • у SSMS, увійдіть у свій сервер
  • перейдіть до "Об'єкт сервера
  • Клацніть правою кнопкою миші на "Пов'язані сервери", потім "Новий пов'язаний сервер"
  • у діалоговому вікні вкажіть будь-яке ім’я вашого пов’язаного сервера: напр .: THISSERVER
  • тип сервера - "Інше джерело даних"
  • Постачальник: Microsoft OLE DB Provider для SQL-сервера
  • Джерело даних: ваш IP, він також може бути лише крапкою (.), Тому що це localhost
  • Перейдіть на вкладку "Безпека" та оберіть 3-ю "Будьте зроблені за допомогою поточного контексту безпеки входу".
  • Ви можете редагувати параметри сервера (3-я вкладка), якщо хочете
  • Натисніть OK, ваш зв'язаний сервер буде створений

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

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

Повірте, це працює, навіть якщо у вас є динамічна вставка в SP2


4

Я знайшов роботу над тим, щоб перетворити один із записів у функцію, що оцінюється в таблиці. Я розумію, що це не завжди можливо, і вводить свої обмеження. Однак мені вдалося завжди знайти хоча б одну з процедур, як хороший кандидат для цього. Мені подобається це рішення, тому що воно не вводить у рішення жодних "хаків".


але недоліком є ​​проблема з обробкою виключень, якщо функція складна, правда?
Муфлікс

2

З цією проблемою я зіткнувся, намагаючись імпортувати результати збереженої програми в тимчасову таблицю, і що Stored Proc вставив у таблицю темп як частину власної операції. Проблема полягає в тому, що SQL Server не дозволяє одному і тому ж процесу записувати до двох різних темп-таблиць одночасно.

Прийнята відповідь OPENROWSET працює чудово, але мені потрібно було уникати використання будь-якого динамічного SQL або зовнішнього постачальника OLE в моєму процесі, тому я пішов іншим маршрутом.

Я знайшов одне просте вирішення - змінити тимчасову таблицю в моїй збереженій процедурі на змінну таблиці. Вона працює точно так само, як і з тимчасовою таблицею, але більше не суперечить моїй іншій вставці таблиці темп.

Просто обезголовлений коментар Я знаю , що деякі з вас збирається писати, попереджаючи мене таблицю змінних , як вбивці продуктивності ... Все , що я можу сказати вам, що в 2020 році виплати дивідендів НЕ боятися таблиці змінних. Якби це був 2008 рік, і моя база даних розміщувалася на сервері з 16 Гб оперативної пам’яті та працює з жорсткими дисками 5400RPM, я можу погодитися з вами. Але це 2020 рік, і я маю SSD масив як основне сховище даних і сотні гігів оперативної пам’яті. Я можу завантажити всю базу даних своєї компанії до змінної таблиці і все ще мати достатню кількість оперативної пам’яті.

Змінні таблиці знову в меню!


1

У мене була така ж проблема та стурбованість дублюючим кодом у двох чи більше відростках. Нарешті я додав додатковий атрибут для "mode". Це дозволило існувати загальному коду всередині одного паростка та потоку та набору результатів проростка, спрямованому на режим.


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

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

Це працює, але я виявив, що він не працює добре з паралельними запитами (асинхронізація та багатокористувацький доступ). Тому зараз я використовую підхід до таблиці темп. Я оновив свою відповідь.
Muflix

1
Логіка таблиці Temp хороша, саме мене посилалися на посилання SProc. Sproc не можна по суті запитувати безпосередньо. Функції, що оцінюються за таблицею, можна безпосередньо запитувати. Мабуть, як ви нагадали у оновленій логіці, найкращим підходом є таблиця темпів, сеанс, екземпляр або глобальна система, яка працює з цього моменту.
GoldBishop

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

Після того, як внутрішній сп виконаний, ваш @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 у мене виникло невідповідність стовпців таблиці, що спричинило помилку відкоту. Він пішов, коли я виправив змінну таблиці 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"

OP запитував про помилку, яка виникає при використанні операторів insert-exec у вкладених збережених процедурах. Ваша проблема поверне іншу помилку, наприклад "Список вибору для оператора INSERT містить менше елементів, ніж список вставок. Кількість значень SELECT має відповідати кількості стовпців INSERT."
Лосбер

Це більше попередження про те, що можна отримати це повідомлення помилково.
користувач3448451
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.