Невозможно использовать инструкцию rollback внутри инструкции insert exec

I have three stored procedures Sp1, Sp2 and Sp3.

The first one (Sp1) will execute the second one (Sp2) and save returned data into @tempTB1 and the second one will execute the third one (Sp3) and save data into @tempTB2.

If I execute the Sp2 it will work and it will return me all my data from the Sp3, but the problem is in the Sp1, when I execute it it will display this error:

INSERT EXEC statement cannot be nested

I tried to change the place of execute Sp2 and it display me another error:

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

SchmitzIT

9,62811 gold badges71 silver badges95 bronze badges

asked Sep 25, 2010 at 19:36

0

This is a common issue when attempting to ‘bubble’ up data from a chain of stored procedures. A restriction in SQL Server is you can only have one INSERT-EXEC active at a time. I recommend looking at How to Share Data Between Stored Procedures which is a very thorough article on patterns to work around this type of problem.

For example a work around could be to turn Sp3 into a Table-valued function.

answered Sep 25, 2010 at 20:18

Eddie GrovesEddie Groves

34.9k14 gold badges49 silver badges48 bronze badges

2

This is the only «simple» way to do this in SQL Server without some giant convoluted created function or executed sql string call, both of which are terrible solutions:

  1. create a temp table
  2. openrowset your stored procedure data into it

EXAMPLE:

INSERT INTO #YOUR_TEMP_TABLE
SELECT * FROM OPENROWSET ('SQLOLEDB','Server=(local);TRUSTED_CONNECTION=YES;','set fmtonly off EXEC [ServerName].dbo.[StoredProcedureName] 1,2,3')

Note: You MUST use ‘set fmtonly off’, AND you CANNOT add dynamic sql to this either inside the openrowset call, either for the string containing your stored procedure parameters or for the table name. Thats why you have to use a temp table rather than table variables, which would have been better, as it out performs temp table in most cases.

answered Jun 1, 2012 at 22:53

6

OK, encouraged by jimhark here is an example of the old single hash table approach: —

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

*/

answered May 16, 2016 at 12:00

3

My work around for this problem has always been to use the principle that single hash temp tables are in scope to any called procs. So, I have an option switch in the proc parameters (default set to off). If this is switched on, the called proc will insert the results into the temp table created in the calling proc. I think in the past I have taken it a step further and put some code in the called proc to check if the single hash table exists in scope, if it does then insert the code, otherwise return the result set. Seems to work well — best way of passing large data sets between procs.

answered May 4, 2016 at 7:27

4

This trick works for me.

You don’t have this problem on remote server, because on remote server, the last insert command waits for the result of previous command to execute. It’s not the case on same server.

Profit that situation for a workaround.

If you have the right permission to create a Linked Server, do it.
Create the same server as linked server.

  • in SSMS, log into your server
  • go to «Server Object
  • Right Click on «Linked Servers», then «New Linked Server»
  • on the dialog, give any name of your linked server : eg: THISSERVER
  • server type is «Other data source»
  • Provider : Microsoft OLE DB Provider for SQL server
  • Data source: your IP, it can be also just a dot (.), because it’s localhost
  • Go to the tab «Security» and choose the 3rd one «Be made using the login’s current security context»
  • You can edit the server options (3rd tab) if you want
  • Press OK, your linked server is created

now your Sql command in the SP1 is

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

Believe me, it works even you have dynamic insert in SP2

answered Nov 22, 2016 at 18:09

ainasiartainasiart

3822 silver badges10 bronze badges

2

I found a work around is to convert one of the prods into a table valued function. I realize that is not always possible, and introduces its own limitations. However, I have been able to always find at least one of the procedures a good candidate for this. I like this solution, because it doesn’t introduce any «hacks» to the solution.

dakab

5,92510 gold badges46 silver badges70 bronze badges

answered Aug 19, 2014 at 1:59

Roman KRoman K

4045 silver badges5 bronze badges

1

I encountered this issue when trying to import the results of a Stored Proc into a temp table, and that Stored Proc inserted into a temp table as part of its own operation. The issue being that SQL Server does not allow the same process to write to two different temp tables at the same time.

The accepted OPENROWSET answer works fine, but I needed to avoid using any Dynamic SQL or an external OLE provider in my process, so I went a different route.

One easy workaround I found was to change the temporary table in my stored procedure to a table variable. It works exactly the same as it did with a temp table, but no longer conflicts with my other temp table insert.

Just to head off the comment I know that a few of you are about to write, warning me off Table Variables as performance killers… All I can say to you is that in 2020 it pays dividends not to be afraid of Table Variables. If this was 2008 and my Database was hosted on a server with 16GB RAM and running off 5400RPM HDDs, I might agree with you. But it’s 2020 and I have an SSD array as my primary storage and hundreds of gigs of RAM. I could load my entire company’s database to a table variable and still have plenty of RAM to spare.

Table Variables are back on the menu!

answered Jan 6, 2020 at 12:39

Geoff GriswaldGeoff Griswald

1,0912 gold badges16 silver badges30 bronze badges

I recommend to read this entire article. Below is the most relevant section of that article that addresses your question:

Rollback and Error Handling is Difficult

In my articles on Error and Transaction Handling in SQL Server, I suggest that you should always have an error handler like

BEGIN CATCH
   IF @@trancount > 0 ROLLBACK TRANSACTION
   EXEC error_handler_sp
   RETURN 55555
END CATCH

The idea is that even if you do not start a transaction in the procedure, you should always include a ROLLBACK, because if you were not able to fulfil your contract, the transaction is not valid.

Unfortunately, this does not work well with INSERT-EXEC. If the called procedure executes a ROLLBACK statement, this happens:

Msg 3915, Level 16, State 0, Procedure SalesByStore, Line 9 Cannot use the ROLLBACK statement within an INSERT-EXEC statement.

The execution of the stored procedure is aborted. If there is no CATCH handler anywhere, the entire batch is aborted, and the transaction is rolled back. If the INSERT-EXEC is inside TRY-CATCH, that CATCH handler will fire, but the transaction is doomed, that is, you must roll it back. The net effect is that the rollback is achieved as requested, but the original error message that triggered the rollback is lost. That may seem like a small thing, but it makes troubleshooting much more difficult, because when you see this error, all you know is that something went wrong, but you don’t know what.

answered Feb 25, 2021 at 2:22

spencer741spencer741

1,2151 gold badge14 silver badges26 bronze badges

3

I had the same issue and concern over duplicate code in two or more sprocs. I ended up adding an additional attribute for «mode». This allowed common code to exist inside one sproc and the mode directed flow and result set of the sproc.

answered Apr 13, 2013 at 20:15

phoenixAZphoenixAZ

4214 silver badges17 bronze badges

what about just store the output to the static table ? Like

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

its not ideal, but its so simple and you don’t need to rewrite everything.

UPDATE:
the previous solution does not work well with parallel queries (async and multiuser accessing) therefore now Iam using temp tables

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

nested spGetData stored procedure content

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

answered Feb 3, 2017 at 12:09

MuflixMuflix

6,81619 gold badges96 silver badges173 bronze badges

3

Declare an output cursor variable to the inner sp :

@c CURSOR VARYING OUTPUT

Then declare a cursor c to the select you want to return.
Then open the cursor.
Then set the reference:

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

DO NOT close or reallocate.

Now call the inner sp from the outer one supplying a cursor parameter like:

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

Once the inner sp executes, your @cOUT is ready to fetch. Loop and then close and deallocate.

demongolem

9,71636 gold badges97 silver badges105 bronze badges

answered May 19, 2017 at 12:31

In my case, I was calling SP1 into SP2 where Insert into #temptable is available and further the output of SP2 I tried to insert into #temtable2 due to which «an insert exec statement cannot be nested» error poped up.

I fixed the issue by placing the final #temptable insertion inside the SP2 itself. So that If we call SP2, it will insert the data into #temptable2 at the end of the execution hene no additional INSERT INTO is not needed outside the execution.

I’m Answering this assuming someone like me will get assistance from his answer.

answered Jul 11, 2023 at 7:31

An option I find useful is an OUT parameter that returns JSON or XML. For example:

CREATE PROCEDURE dbo.Sp2 @jsonOut NVARCHAR(MAX) OUT
AS

DECLARE @tempTB1 TABLE (/* ...specify columns needed... */)

INSERT INTO @tempTB1
EXEC Sp3

SET @jsonOut = (SELECT * FROM @tempTB1 FOR JSON PATH, INCLUDE_NULL_VALUES)

Now Sp1 can get the data as follows:

DECLARE @jsonOut NVARCHAR(MAX)
EXEC Sp2 @jsonOut OUT

If the columns of your output are dynamic, it may be very difficult to work with the output, but for simpler data it’s handy.

answered Jun 25, 2024 at 8:28

BloopyBloopy

4292 silver badges9 bronze badges

On SQL Server 2008 R2, I had a mismatch in table columns that caused the Rollback error. It went away when I fixed my sqlcmd table variable populated by the insert-exec statement to match that returned by the stored proc. It was missing org_code. In a windows cmd file, it loads result of stored procedure and selects it.

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"

answered Nov 29, 2016 at 6:16

2

If you are able to use other associated technologies such as C#, I suggest using the built in SQL command with Transaction parameter.

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

I’ve created a simple Console App that demonstrates this ability which can be found here:
https://github.com/hecked12/SQL-Transaction-Using-C-Sharp

In short, C# allows you to overcome this limitation where you can inspect the output of each stored procedure and use that output however you like, for example you can feed it to another stored procedure. If the output is ok, you can commit the transaction, otherwise, you can revert the changes using rollback.

answered Jan 6, 2020 at 5:19

Anas NajaaAnas Najaa

351 silver badge10 bronze badges

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.

Table of Contents

SQL Server Error: 3915
Severity: 16
Event Logged or not: No
Description:
Cannot use the ROLLBACK statement within an INSERT-EXEC statement.
Severity 16 Description:
Indicates general errors that can be corrected by the user.

Reading sql server error log location from SQL Query

Identifying SQL Server Error Log File used by SQL Server Database Engine can be done by reading SQL Server Error Logs. DBA can execute the XP_READERRORLOG extended stored procedure to read the SQL Server Error Log and search for its location used by the instance of SQL Server.

USE master
Go
xp_readerrorlog 0, 1, N'Logging SQL Server messages in file', NULL, NULL, N'asc'
Go

The parameters for XP_READERRRORLOG are:
1. Value of error log file we would like to read. values are 0 = current, 1 = last one before current, 2 = second last before current etc…
2. Log file type:- 1 or NULL = error log, 2 = SQL Agent log
3. Search string 1:- String one you want to search for
4. Search string 2:- String two you want to search for to further refine the results
5. start time for Search
6. end time for search
7. Sort order for search results:- N’asc’ = ascending, N’desc’ = descending

By default, we have 6 Server Error Logs kept but we can increase the number of SQL Server Error Logs from the default value of six.

For other ways to read and find error log location please our artcile https://sqlserver-dba.co.uk/error-log/sql-server-identify-location-of-the-sql-server-error-log-file.html

Solution for Resolving the Error

Alternate Solutions

  1. Restarting SQL Server Service(non production instances only)

  • To Restart, Start or Stop the SQL Server instance by right click on sql server instance in SSMS or in SQL. You may need to open SSMS as administrator to start, stop the instance.

  • Other ways for restarting SQL server Service

  1. From SQL Configuration manager from Start menu
  2. From Services in Windows server
  3. From Cmd using net start and net stop

2.Checking SQL Performance metrics like CPU, Memory

Check SQL Server CPU, Memory usage, longest running queries, deadlocks etc.. using activity monitor or sp_who2.

To view Activity Monitor in SQL Server 2005 and in SQL Server 2008, a user must have VIEW SERVER STATE permission.

2 Different Ways to Open up Activity Monitor in SQL Server 2008 are mentioned below:

Open up Activity Monitor Using Object Explorer

In Object Explorer, right click the SQL Server 2008 Instance and click on Activity Monitor.

Also can be opened from SQL Server 2008 Management Studio’s toolbar, by clicking Activity Monitor

SSMS Activity Monitor by Method2

It shows the graphical display of Processor Time (%), Number of Waiting Tasks, Database I/O (MB/Sec) and the Number of Batch Requests/second.

For information on SQL Server Activity monitor go to https://sqlserver-dba.co.uk/sql-server-administration-basics/activity-monitor

Or using SQL Query analyzer window to run sp_who2 command which is less resource intensive and gives same information as activity monitor.

2.Checking Windows Performance metrics like CPU, Memory, Disk Space etc.

  1. Open task manager to check CPU, Memory usage etc.
  2. Open file explorer to check Disk space on each drive.

SQL Server Error Code and solution summary

SQL Server Error: 3915
Severity: 16
Event Logged or not: No
Description:
Cannot use the ROLLBACK statement within an INSERT-EXEC statement.


Рейтинг 4.50/4:

66 / 9 / 4

Регистрация: 09.01.2018

Сообщений: 266

Вывод в таблицу результата исполнения хранимой процедуры

06.09.2023, 08:53. Показов 859. Ответов 12


Добрый день. Воспользовался известным подходом по теме:

1. объявляем/генерим таблицу с нужным набором полей
2. insert таблица from exec хранимая процедура

Все бы хорошо, но в ХП есть try catch с commit rollback. о чем SSMS радостно сообщает
Cannot use the ROLLBACK statement within an INSERT-EXEC statement.

ХП менять нельзя. Подскажите, как быть?



0



Vala Valechka Valusha

158 / 244 / 25

Регистрация: 11.08.2022

Сообщений: 2,587

06.09.2023, 13:35

Сообщение от vpivo

Все бы хорошо, но в ХП есть try catch с commit rollback. о чем SSMS радостно сообщает
Cannot use the ROLLBACK statement within an INSERT-EXEC statement.

Похоже там есть ещё транзакция в процедуре.



0



66 / 9 / 4

Регистрация: 09.01.2018

Сообщений: 266

06.09.2023, 20:51

 [ТС]

Конечно есть.

Добавлено через 24 секунды

Сообщение от Ludwig Valentin

Похоже там есть ещё транзакция в процедуре.

Конечно есть.



0



1234 / 343 / 93

Регистрация: 14.10.2022

Сообщений: 1,039

07.09.2023, 08:38

Ну, как описано выше — выход один.
loopback связанный сервер и openrowset из него.



0



Vala Valechka Valusha

158 / 244 / 25

Регистрация: 11.08.2022

Сообщений: 2,587

07.09.2023, 10:39

Сообщение от PaulWist

«INSERT EXEC statement cannot be nested.» and «Cannot use the ROLLBACK statement within an INSERT-EXEC statement.»

INSERT EXEC не может быть вложенным.
И нельзя использовать ROLLBACK вместе с INSERT-EXEC.



0



andrey197888888

14 / 13 / 1

Регистрация: 17.04.2023

Сообщений: 107

19.09.2023, 08:25

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
EXEC sp_configure 'show advanced options', 1
RECONFIGURE RECONFIGURE WITH override
EXEC sp_configure 'Ad Hoc Distributed Queries', 1
RECONFIGURE RECONFIGURE WITH override
GO
SELECT * INTO #TmpTable FROM OPENROWSET(
'SQLOLEDB',
'Server=?;Trusted_Connection=Yes;',                           ---имя сервера
'SET FMTONLY OFF EXEC [?]    ?')                                 ---наименование процедуры [] и значение переменной
GO
EXEC sp_configure 'ad hoc distributed queries', 0
RECONFIGURE
EXEC sp_configure 'show advanced options', 0
RECONFIGURE
 
 
SELECT * FROM #TmpTable



0



Vala Valechka Valusha

158 / 244 / 25

Регистрация: 11.08.2022

Сообщений: 2,587

19.09.2023, 10:16

andrey197888888,

А хорошо ли открывать доступ к advanced options, чтобы практически сразу его закрыть?
А что, если процедура будет исполняться часто?



0



14 / 13 / 1

Регистрация: 17.04.2023

Сообщений: 107

19.09.2023, 10:36

На усмотрение пользователя.



0



Vala Valechka Valusha

158 / 244 / 25

Регистрация: 11.08.2022

Сообщений: 2,587

19.09.2023, 10:39

Сообщение от andrey197888888

На усмотрение пользователя.

А политически?
Или по соображениям той же безопасности?



0



14 / 13 / 1

Регистрация: 17.04.2023

Сообщений: 107

19.09.2023, 16:23

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

Добавлено через 4 часа 55 минут
Забыл добавить. В моем примере предварительно временную таблицу создавать не нужно, она создаётся автоматически.



1



1234 / 343 / 93

Регистрация: 14.10.2022

Сообщений: 1,039

19.09.2023, 17:24

Да тут вопрос вообще в том, а нафига опция ‘ad hoc distributed queries’ то сбрасывается, то устанавливается?
Какой в этом глубинный смысл?

Ее нужно установить один раз, и забыть о ней.
В чем проблема то, если вы правильно настроили безопасность?



0



14 / 13 / 1

Регистрация: 17.04.2023

Сообщений: 107

19.09.2023, 17:33

Я ж написал, на усмотрение пользователя. Если не надо — пусть не отключает. Если надо — пусть отключает. Странные вопросы какие то задаются, причем не от автора топика. Пиши свой вариант ответа, обосновывай. У меня проблем нет.



0



inter-admin

Эксперт

29715 / 6470 / 2152

Регистрация: 06.03.2009

Сообщений: 28,500

Блог

19.09.2023, 17:33

Помогаю со студенческими работами здесь

Вывод результата хранимой процедуры в ComboBox
Уважаемые коллеги.
Есть хранимая процедура, которая выводит имена столбцов таблицы в БД:

ALTER PROCEDURE GetNameColumns
AS …

Запуск хранимой процедуры через C# и вывод результата в DataGrid
Доброго времени суток, уважаемое сообщество! Прошу помощи в нахождении ошибки.
есть таблица в mssql. есть хранимая процедура, которая…

Привязка выполнения хранимой процедуры к кнопке и вывод результата в listbox
Добрый вечер, есть хранимая процедура, хочется привязать ее выполнение к кнопке и сделать вывод результата в listbox1
Разбираюсь в…

Привязка выполнения хранимой процедуры к кнопке и вывод результата в listbox
Добрый вечер, есть хранимая процедура, хочется привязать ее выполнение к кнопке и сделать вывод результата в listbox1
Разбираюсь в…

Возврат результата Хранимой процедуры
Всем привет!

Нужна помощь, никак не соображу, как правильно сделать. Вероятнее всего что-то не так делаю, прошу навести на путь…

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:

13

Новые блоги и статьи

Все статьи  

Все блоги / 

JWT аутентификация в Java

Javaican 21.04.2025

JWT (JSON Web Token) представляет собой открытый стандарт (RFC 7519), который определяет компактный и самодостаточный способ передачи информации между сторонами в виде JSON-объекта. Эта информация. . .

Спринты Agile: Планирование, выполнение, ревью и ретроспектива

EggHead 21.04.2025

Спринты — сердцевина Agile-методологии, позволяющая командам создавать работающий продукт итерационно, с постоянной проверкой гипотез и адаптацией к изменениям. В основе концепции спринтов лежит. . .

Очередные открытия мега простых чисел, сделанные добровольцами с помощью домашних компьютеров

Programma_Boinc 21.04.2025

Очередные открытия мега простых чисел, сделанные добровольцами с помощью домашних компьютеров.

3 марта 2025 года, в результате обобщенного поиска простых чисел Ферма в PrimeGrid был найден. . .

Система статов в Unity

GameUnited 20.04.2025

Статы — фундаментальный элемент игрового дизайна, который определяет характеристики персонажей, предметов и других объектов в игровом мире. Будь то показатель силы в RPG, скорость передвижения в. . .

Статические свойства и методы в TypeScript

run.dev 20.04.2025

TypeScript прочно занял своё место в системе современной веб-разработки. Этот строго типизированный язык программирования не просто расширяет возможности JavaScript — он делает разработку более. . .

Batch Transform и Batch Gizmo Drawing API в Unity

GameUnited 20.04.2025

В мире разработки игр и приложений на Unity производительность всегда была критическим фактором успеха. Создатели игр постоянно балансируют между визуальной привлекательностью и плавностью работы. . .

Звук в Unity: Рандомизация с Audio Random Container

GameUnited 20.04.2025

В современных играх звуковое оформление часто становится элементом, который либо полностью погружает игрока в виртуальный мир, либо разрушает атмосферу за считанные минуты. Представьте: вы исследуете. . .

Максимальная производительность C#: Советы, тестирование и заключение

stackOverflow 20.04.2025

Погружение в мир микрооптимизаций C# открывает перед разработчиком целый арсенал мощных техник. Но как определить, где и когда их применять? Ответ начинается с точных измерений и профилирования.

. . .

Максимальная производительность C#: Предсказание ветвлений

stackOverflow 20.04.2025

Третий ключевой аспект низкоуровневой оптимизации — предсказание ветвлений. Эта тема менее известна среди разработчиков, но её влияние на производительность может быть колоссальным. Чтобы понять. . .

Максимальная производительность C#: Векторизация (SIMD)

stackOverflow 20.04.2025

Помимо работы с кэшем, другим ключевым аспектом низкоуровневой оптимизации является векторизация вычислений. SIMD (Single Instruction, Multiple Data) позволяет обрабатывать несколько элементов данных. . .

Наверх

I understand there is a regularly quoted answer that is meant to address this question, but I believe there is not enough explanation on that thread to really answer the question.

Why earlier answers are inadequate

The first (and accepted) answer simply says this is a common problem and talks about having only one active insert-exec at a time (which is only the first half of the question asked there and doesn’t address the ROLLBACK error). The given workaround is to use a table-valued function — which does not help my scenario where my stored procedure needs to update data before returning a result set.

The second answer talks about using openrowset but notes you cannot dynamically specify argument values for the stored procedure — which does not help my scenario because different users need to call my procedure with different parameters.

The third answer provides something called «the old single hash table approach» but does not explain whether it is addressing part 1 or 2 of the question, nor how it works, nor why.

No answer explains why the database is giving this error in the first place.

My use case / requirements

To give specifics for my scenario (although simplified and generic), I have procedures something like below.

In a nutshell though — the first procedure will return a result set, but before it does so, it updates a status column. Effectively these records represent records that need to be synchronised somewhere, so when you call this procedure the procedure will flag the records as being «in progress» for sync.

The second stored procedure calls that first one. Of course the second stored procedure wants to take those records and perform inserts and updates on some tables — to keep those tables in sync with whatever data was returned from the first procedure. After performing all the updates, the second procedure then calls a third procedure — within a cursor (ie. row by row on all the rows in the result set that was received from the first procedure) — for the purpose of setting the status on the source data to «in sync». That is, one by one it goes back and says «update the sync status on record id 1, to ‘in sync'» … and then record 2, and then record 3, etc.

The issue I’m having is that calling the second procedure results in the error

Msg 50000, Level 16, State 1, Procedure getValuesOuterCall, Line 484 [Batch Start Line 24]
Cannot use the ROLLBACK statement within an INSERT-EXEC statement.

but calling the first procedure directly causes no error.

Procedure 1

-- Purpose here is to return a result set,
-- but for every record in the set we want to set a status flag
-- to another value as well.

alter procedure getValues @username, @password, @target

as
begin
  set xact_abort on;
  begin try
    begin transaction;
      declare @tableVariable table (
        ...
      );

      update someOtherTable
      set something = somethingElse
      output 
        someColumns
      into @tableVariable
      from someTable
      join someOtherTable
      join etc
      where someCol = @username
      and etc
      ;

      select 
        someCols
      from @tableVariable
      ;
    commit;
  end try

  begin catch
    if @@trancount > 0 rollback;
    declare @msg nvarchar(2048) = error_message() + ' Error line: ' + CAST(ERROR_LINE() AS nvarchar(100));
    raiserror (@msg, 16, 1);
    return 55555
  end catch
end

Procedure 2

-- Purpose here is to obtain the result set from earlier procedure
-- and then do a bunch of data updates based on the result set.
-- Lastly, for each row in the set, call another procedure which will
-- update that status flag to another value.

alter procedure getValuesOuterCall @username, @password, @target

as
begin
  set xact_abort on;
  begin try
    begin transaction;

      declare @anotherTableVariable

      insert into @anotherTableVariable
      exec getValues @username = 'blah', @password = @somePass, @target = ''
      ;

      with CTE as (
        select someCols
        from @anotherTableVariable
        join someOtherTables, etc;
      )
      merge anUnrelatedTable as target
      using CTE as source
      on target.someCol = source.someCol
      when matched then update 
        target.yetAnotherCol = source.yetAnotherCol,
        etc
      when not matched then
        insert (someCols, andMoreCols, etc)
        values ((select someSubquery), source.aColumn, source.etc)
      ;

      declare @myLocalVariable int;
      declare @mySecondLocalVariable int;

      declare lcur_myCursor cursor for
        select keyColumn
        from @anotherTableVariable
        ;

      open lcur_muCursor;

      fetch lcur_myCursor into @myLocalVariable;

      while @@fetch_status = 0
        begin
          select @mySecondLocalVariable = someCol 
          from someTable 
          where someOtherCol = @myLocalVariable;

          exec thirdStoredProcForSettingStatusValues @id = @mySecondLocalVariable, etc
        end

      deallocate lcur_myCursor;
  
    commit;
  end try

  begin catch
    if @@trancount > 0 rollback;
    declare @msg nvarchar(2048) = error_message() + ' Error line: ' + CAST(ERROR_LINE() AS nvarchar(100));
    raiserror (@msg, 16, 1);
    return 55555
  end catch
end

The parts I don’t understand

Firstly, I have no explicit ‘rollback’ (well, except in the catch block) — so I have to presume that an implicit rollback is causing the issue — but it is difficult to understand where the root of this problem is; I am not even entirely sure which stored procedure is causing the issue.

Secondly, I believe the statements to set xact_abort and begin transaction are required — because in procedure 1 I am updating data before returning the result set. In procedure 2 I am updating data before I call a third procedure to update further data.

Thirdly, I don’t think procedure 1 can be converted to a table-valued function because the procedure performs a data update (which would not be allowed in a function?)

Things I have tried

I removed the table variable from procedure 2 and actually created a permanent table to store the results coming back from procedure 1. Before calling procedure 1, procedure 2 would truncate the table. I still got the rollback error.

I replaced the table variable in procedure 1 with a temporary table (ie. single #). I read the articles about how such a table persists for the lifetime of the connection, so within procedure 1 I had drop table if exists... and then create table #.... I still got the rollback error.

Lastly

I still don’t understand exactly what is the problem — what is Microsoft struggling to accomplish here? Or what is the scenario that SQL Server cannot accommodate for a requirement that appears to be fairly straightforward: One procedure returns a result set. The calling procedure wants to perform actions based on what’s in that result set. If the result set is scoped to the first procedure, then why can’t SQL Server just create a temporary copy of the result set within the scope of the second procedure so that it can be acted upon?

Or have I missed it completely and the issue has something to do with the final call to a third procedure, or maybe to do with using try ... catch — for example, perhaps the logic is totally fine but for some reason it is hitting the catch block and the rollback there is the problem (ie. so if I fix the underlying reason leading us to the catch block, all will resolve)?

I have three stored procedures Sp1, Sp2 and Sp3.

The first one (Sp1) will execute the second one (Sp2) and save returned data into @tempTB1 and the second one will execute the third one (Sp3) and save data into @tempTB2.

If I execute the Sp2 it will work and it will return me all my data from the Sp3, but the problem is in the Sp1, when I execute it it will display this error:

INSERT EXEC statement cannot be nested

I tried to change the place of execute Sp2 and it display me another error:

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

SchmitzIT

9,1549 gold badges65 silver badges92 bronze badges

asked Sep 25, 2010 at 19:36

0

This is a common issue when attempting to ‘bubble’ up data from a chain of stored procedures. A restriction in SQL Server is you can only have one INSERT-EXEC active at a time. I recommend looking at How to Share Data Between Stored Procedures which is a very thorough article on patterns to work around this type of problem.

For example a work around could be to turn Sp3 into a Table-valued function.

answered Sep 25, 2010 at 20:18

Eddie GrovesEddie Groves

33.7k14 gold badges47 silver badges48 bronze badges

2

This is the only «simple» way to do this in SQL Server without some giant convoluted created function or executed sql string call, both of which are terrible solutions:

  1. create a temp table
  2. openrowset your stored procedure data into it

EXAMPLE:

INSERT INTO #YOUR_TEMP_TABLE
SELECT * FROM OPENROWSET ('SQLOLEDB','Server=(local);TRUSTED_CONNECTION=YES;','set fmtonly off EXEC [ServerName].dbo.[StoredProcedureName] 1,2,3')

Note: You MUST use ‘set fmtonly off’, AND you CANNOT add dynamic sql to this either inside the openrowset call, either for the string containing your stored procedure parameters or for the table name. Thats why you have to use a temp table rather than table variables, which would have been better, as it out performs temp table in most cases.

answered Jun 1, 2012 at 22:53

5

OK, encouraged by jimhark here is an example of the old single hash table approach: —

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

*/

answered May 16, 2016 at 12:00

2

My work around for this problem has always been to use the principle that single hash temp tables are in scope to any called procs. So, I have an option switch in the proc parameters (default set to off). If this is switched on, the called proc will insert the results into the temp table created in the calling proc. I think in the past I have taken it a step further and put some code in the called proc to check if the single hash table exists in scope, if it does then insert the code, otherwise return the result set. Seems to work well — best way of passing large data sets between procs.

answered May 4, 2016 at 7:27

4

This trick works for me.

You don’t have this problem on remote server, because on remote server, the last insert command waits for the result of previous command to execute. It’s not the case on same server.

Profit that situation for a workaround.

If you have the right permission to create a Linked Server, do it.
Create the same server as linked server.

  • in SSMS, log into your server
  • go to «Server Object
  • Right Click on «Linked Servers», then «New Linked Server»
  • on the dialog, give any name of your linked server : eg: THISSERVER
  • server type is «Other data source»
  • Provider : Microsoft OLE DB Provider for SQL server
  • Data source: your IP, it can be also just a dot (.), because it’s localhost
  • Go to the tab «Security» and choose the 3rd one «Be made using the login’s current security context»
  • You can edit the server options (3rd tab) if you want
  • Press OK, your linked server is created

now your Sql command in the SP1 is

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

Believe me, it works even you have dynamic insert in SP2

answered Nov 22, 2016 at 18:09

ainasiartainasiart

3822 silver badges10 bronze badges

2

I found a work around is to convert one of the prods into a table valued function. I realize that is not always possible, and introduces its own limitations. However, I have been able to always find at least one of the procedures a good candidate for this. I like this solution, because it doesn’t introduce any «hacks» to the solution.

dakab

5,2999 gold badges43 silver badges65 bronze badges

answered Aug 19, 2014 at 1:59

Roman KRoman K

3944 silver badges5 bronze badges

1

I encountered this issue when trying to import the results of a Stored Proc into a temp table, and that Stored Proc inserted into a temp table as part of its own operation. The issue being that SQL Server does not allow the same process to write to two different temp tables at the same time.

The accepted OPENROWSET answer works fine, but I needed to avoid using any Dynamic SQL or an external OLE provider in my process, so I went a different route.

One easy workaround I found was to change the temporary table in my stored procedure to a table variable. It works exactly the same as it did with a temp table, but no longer conflicts with my other temp table insert.

Just to head off the comment I know that a few of you are about to write, warning me off Table Variables as performance killers… All I can say to you is that in 2020 it pays dividends not to be afraid of Table Variables. If this was 2008 and my Database was hosted on a server with 16GB RAM and running off 5400RPM HDDs, I might agree with you. But it’s 2020 and I have an SSD array as my primary storage and hundreds of gigs of RAM. I could load my entire company’s database to a table variable and still have plenty of RAM to spare.

Table Variables are back on the menu!

answered Jan 6, 2020 at 12:39

I recommend to read this entire article. Below is the most relevant section of that article that addresses your question:

Rollback and Error Handling is Difficult

In my articles on Error and Transaction Handling in SQL Server, I suggest that you should always have an error handler like

BEGIN CATCH
   IF @@trancount > 0 ROLLBACK TRANSACTION
   EXEC error_handler_sp
   RETURN 55555
END CATCH

The idea is that even if you do not start a transaction in the procedure, you should always include a ROLLBACK, because if you were not able to fulfil your contract, the transaction is not valid.

Unfortunately, this does not work well with INSERT-EXEC. If the called procedure executes a ROLLBACK statement, this happens:

Msg 3915, Level 16, State 0, Procedure SalesByStore, Line 9 Cannot use the ROLLBACK statement within an INSERT-EXEC statement.

The execution of the stored procedure is aborted. If there is no CATCH handler anywhere, the entire batch is aborted, and the transaction is rolled back. If the INSERT-EXEC is inside TRY-CATCH, that CATCH handler will fire, but the transaction is doomed, that is, you must roll it back. The net effect is that the rollback is achieved as requested, but the original error message that triggered the rollback is lost. That may seem like a small thing, but it makes troubleshooting much more difficult, because when you see this error, all you know is that something went wrong, but you don’t know what.

answered Feb 25, 2021 at 2:22

spencer741spencer741

9551 gold badge10 silver badges22 bronze badges

3

I had the same issue and concern over duplicate code in two or more sprocs. I ended up adding an additional attribute for «mode». This allowed common code to exist inside one sproc and the mode directed flow and result set of the sproc.

answered Apr 13, 2013 at 20:15

phoenixAZphoenixAZ

4193 silver badges17 bronze badges

what about just store the output to the static table ? Like

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

its not ideal, but its so simple and you don’t need to rewrite everything.

UPDATE:
the previous solution does not work well with parallel queries (async and multiuser accessing) therefore now Iam using temp tables

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

nested spGetData stored procedure content

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

answered Feb 3, 2017 at 12:09

MuflixMuflix

5,95215 gold badges74 silver badges151 bronze badges

3

Declare an output cursor variable to the inner sp :

@c CURSOR VARYING OUTPUT

Then declare a cursor c to the select you want to return.
Then open the cursor.
Then set the reference:

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

DO NOT close or reallocate.

Now call the inner sp from the outer one supplying a cursor parameter like:

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

Once the inner sp executes, your @cOUT is ready to fetch. Loop and then close and deallocate.

demongolem

9,42036 gold badges90 silver badges105 bronze badges

answered May 19, 2017 at 12:31

If you are able to use other associated technologies such as C#, I suggest using the built in SQL command with Transaction parameter.

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

I’ve created a simple Console App that demonstrates this ability which can be found here:
https://github.com/hecked12/SQL-Transaction-Using-C-Sharp

In short, C# allows you to overcome this limitation where you can inspect the output of each stored procedure and use that output however you like, for example you can feed it to another stored procedure. If the output is ok, you can commit the transaction, otherwise, you can revert the changes using rollback.

answered Jan 6, 2020 at 5:19

On SQL Server 2008 R2, I had a mismatch in table columns that caused the Rollback error. It went away when I fixed my sqlcmd table variable populated by the insert-exec statement to match that returned by the stored proc. It was missing org_code. In a windows cmd file, it loads result of stored procedure and selects it.

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"

answered Nov 29, 2016 at 6:16

2

18. сентября 2011 00:09

В SQL Server есть ограничение на инструкцию INSERT EXEC — она не может быть вложенной. Т.е. если в теле процедуры мы уже используем код INSERT EXEC, то рекордсет из этой процедуры мы не сможем вставить в таблицу. На Microsoft Connect есть фитбек с этой проблемой (Cannot have nested INSERT … EXEC) и совсем недавно эту проблему закрыли с пометкой as Won’t Fix.

Но, что делать, если нам все-таки необходимо вывести результат работы процедуры в таблицу? Именно тому, как обойти одно из ограничений сиквела и посвящён этот пост.

Для иллюстрации создадим тестовую БД TestDB и две процедуры, одна будет возвращать небольшой рекордсет, а во второй мы будем вызывать эту процедуру с использованием инструкции INSERT EXEC.

create database TestDB
go

use TestDB
go

if object_id ( 'dbo.TestProc01', 'P' ) is not null
  drop procedure dbo.TestProc01 
go

create procedure dbo.TestProc01
as
set nocount on
declare @t table ( i int )
insert into @t
values (1),(2),(3)
select * from @t
go

if object_id ( 'dbo.TestProc02', 'P' ) is not null
  drop procedure dbo.TestProc02 
go

create procedure dbo.TestProc02
as
set nocount on
declare @t table ( i int )
insert into @t
exec dbo.TestProc01
select * from @t
go

Дальше небольшой скрипт, который и эмулирует, озвученную выше, ошибку:

declare @t table ( i int )
insert into @t
exec dbo.TestProc02

А теперь несколько способов обойти эту ошибку:

1) Первый и самый правильный
По возможности не использовать вложенных инструкций INSERT EXEC, либо вместо вызова процедуры, в которой уже используется такая конструкция, взять часть скрипта из тела этой самой процедуры. Как правило, это достаточно легко можно реализовать. В моём примере достаточно заменить вызов процедуры dbo.TestProc02 на dbo.TestProc01.

2) Используем OPENQUERY или OPENROWSET
Для этого нам потребуется создать Linked Server с ссылкой нашего сервера БД на самого себя ( в моём случае это IP 127.0.0.1).

use master
go

exec sp_addlinkedserver N'127.0.0.1'
                      , N'SQL Server';
go

use TestDB
go

declare @t table ( i int )
insert into @t
select * from OpenQuery ( [127.0.0.1], 'TestDB.dbo.TestProc02' )
select * from @t

3) Используем распределённый запрос

declare @t table ( i int )
insert into @t
exec [127.0.0.1].TestDB.dbo.TestProc02
select * from @t
go
--либо:
declare @t table ( i int )
insert into @t
exec ( 'TestDB.dbo.TestProc02' ) at [127.0.0.1]
select * from @t
go

Не забываем включить службу Координатор распределенных транзакций Иначе получим ошибку:

4) Используем процедуру xp_cmdshell и утилиту SQLCMD
Но для начала включим использование процедуры xp_cmdshell

exec sp_configure 'show advanced options', 1
reconfigure
exec sp_configure 'xp_cmdshell', 1
reconfigure
go

А теперь выгружаем результат работы процедуры dbo.TestProc02 в таблицу:

declare @t table ( val varchar(100) )
insert into @t
exec master..xp_cmdshell 'sqlcmd -E -q "exec TestDB.dbo.TestProc02" -h -1 -W'

select val from @t
where val is not null

5) Используем процедуру xp_cmdshell и утилиту BCP

--Выгружаем результат процедуры dbo.TestProc02 на диск
exec xp_cmdshell 'bcp "exec TestDB.dbo.TestProc02" queryout "c:tempTest.txt" -T -c -C RAW -rn -tchar(3)'
--Создадим таблицу для получения результата 
if object_id ( 'dbo.tmpMyResult', 'U' ) is not null
drop table tmpMyResult 
go 
create table tmpMyResult ( val int )
--Загружаем результат с диска в таблицу
exec xp_cmdshell 'bcp TestDB.dbo.tmpMyResult in "c:tempTest.txt" -T -c -C RAW -rn -tchar(3)'  
--Смотрим
select * from tmpMyResult

6) Используем CLR
Но этот вариант я не буду рассматривать в рамках этого поста.

  • Remove From My Forums
  • Question

  • Hello!

    I have a problem with executing Transact-SQL at SQL2005. To disable database distribution options, folowing code is used:

    Code Snippet

    USE master

    CREATE TABLE #om_tmp_distdb
    (
     [Name] sysname,
     min_distretention int,
     max_distretention int,
     history_retention int,
     history_cleanup_agent sysname,
     dist_cleanup_agent sysname,
     status int,
     data_folder sysname,
     data_file sysname,
     data_file_size int,
     log_folder sysname,
     log_file sysname,
     log_file_size int
    )

    ————————————————

    — GETTING INFO ABOUT Repl. SETTINGS

    ————————————————

    INSERT INTO #om_tmp_distdb

    EXEC sp_helpdistributiondb

    ————————————————

    DECLARE @distDbName sysname

    SELECT @distDbName = name COLLATE DATABASE_DEFAULT

    FROM #om_tmp_distdb

    if @distDbName COLLATE DATABASE_DEFAULT IS NOT NULL  exec sp_dropdistributiondb @distDbName

    IF @@ERROR = 0 exec sp_dropdistributor

    DROP TABLE #om_tmp_distdb

    Under SQL2K it works successfully, but when I run it at SQL2005, the error occurs:

    An INSERT EXEC statement cannot be nested.

    Please explain, what is a source of the problem?

Answers

  • Here i given the sample workaround..

    Before execute the following code you have to change few settings (script given as bellow)

    Prerequisite

    EXEC sys.sp_configure N‘show advanced options’, N‘1’  RECONFIGURE WITH OVERRIDE

    go

    EXEC sys.sp_configure N‘Ad Hoc Distributed Queries’, N‘1’

    go

    RECONFIGURE WITH OVERRIDE

    go

    EXEC sys.sp_configure N‘show advanced options’, N‘0’  RECONFIGURE WITH OVERRIDE

    go

    Workaround — Code

    Create Proc InsertIntoSp

    as

    Begin

                Set NoCount ON

                Declare  @Script Table

                (

                            Lines varchar(max)

                );

                Insert Into @Script

                            Exec sp_helptext ‘sp_helptext’

                Select * From @Script;

    End

    Go

    Create Table #MyScriptTable

                (

                            Lines varchar(max)

                );

    —Fail

    Insert Into #MyScriptTable

                Exec master..InsertIntoSp

    /*

    Error Message:

    Msg 8164, Level 16, State 1, Procedure InsertIntoSp, Line 10

    An INSERT EXEC statement cannot be nested.

    */

    —Workaround

    Insert Into #MyScriptTable

    SELECT a.*

    FROM OPENROWSET(‘MSDASQL’,‘DRIVER={SQL Server}; SERVER=YourServerName; UID=sa; PWD=PASSWORD’,  ‘Exec master..InsertIntoSp’) AS a;

    Select * from #MyScriptTable

    If there are lot place you have to implement this, then better you can use the LinkedServer which is pointing to the same server.

     Adv: You can suppress the ConnectionString.

  • Hi David,

    Try using «set fmtonly off;» at the beginning of your script.

    SELECT *

    FROM OPENROWSET

    (

    ‘MSDASQL’,

    ‘Driver={SQL Server};Server=localhost’,

    ‘SET FMTONLY OFF;…’

    )

    AMB

  • Hi David,

    > Although I don’t understand, why SET FMTONLY OFF required

    You can use «Profiler» to see what exactly in being executed by SQL Server. There you will notice that «set fmtonly on» is part of the script and this does not work when you have temporary tables inside your sp. That is why including «set fmtonly off» as part of the script helps.

    AMB

  • Well, I found solution myself…

    To avoid processing multiple Recordsets, you may use SQLOLEDB provider instead of MSDASQL. The synthax of Connection string I found here: http://www.dbforums.com/showthread.php?t=1190454#post4448538 (thanks to Steve Kass).

    So, code must look as follows:

    Code Snippet

    SELECT *

    FROM OPENROWSET

    (

    ‘SQLOLEDB’,

    ‘Server=localhost;Trusted_Connection=YES’,

    ‘ SET FMTONLY OFF exec sp_helpdistpublisher SELECT NULL as name,NULL,NULL….’

    )

    WHERE name IS NOT NULL

    If sp-helpdistpublisher returns recordset, it’s selected out. Otherwise second select (consisting of NULL’s) is selected out, which than is filtered by WHERE clause and returns a recordset with no rows. The error then is avoided.

  • Remove From My Forums
  • Question

  • i have a 3 or 4 cursors, and in the inner cursor i am inserting into a table from a sproc. i keep getting the error

    An INSERT EXEC statement cannot be nested.

    heres the actual insert code:

    set @SQLString = ‘EXEC ScoresGetlines ‘+cast(@customerID as char(10))+‘,’ + cast(@programId as char(10))+‘,’ + ‘»‘+ @period +‘»,NULL,NULL,0’

    INSERT INTO reportData

    exec (@sqlString)

    ive tried just a simple :

    insert into reportdata

    exec scoreGetLines @customerId,@programID………..

    that still doesnt work. same error. how can this be sorted

Answers

    • Marked as answer by

      Friday, July 9, 2010 11:22 PM

  • can you please put an example of how you changed the stored procedures into functions to make this work? openquery is not an option for me.

    That is not possible if any kind of database change is involved. You cannot change the state of the database from a function, you can do it from a stored procedure. Also, you cannot use INSERT EXEC within a function.

    Following scripts present 3 workarounds: 

               OPENQUERY
               Remove INSERT EXEC from sproc           

               bcp — BULK INSERT

    Not pretty, but they do work.

    -- SQL Server 2008 T-SQL INSERT-EXEC nesting issue and workarounds
    USE tempdb;
    GO
    -- SELECT INTO create empty tables for testing
    SELECT TOP (0) * INTO Alpha
    FROM OPENQUERY(DELLSTAR,'EXECUTE [AdventureWorks].[dbo].[uspGetBillOfMaterials] 800, ''2004-02-01''')
    GO 
    /*
    CREATE TABLE [dbo].[Alpha](
    	[ProductAssemblyID] [int] NULL,
    	[ComponentID] [int] NULL,
    	[ComponentDesc] [nvarchar](50) NULL,
    	[TotalQuantity] [numeric](38, 2) NULL,
    	[StandardCost] [money] NULL,
    	[ListPrice] [money] NULL,
    	[BOMLevel] [smallint] NULL,
    	[RecursionLevel] [int] NULL
    ) ON [PRIMARY]
    */
    
    CREATE PROC sprocINSERTEXEC 
    AS 
      BEGIN 
        SET NoCount ON 
       DECLARE @OMEGA TABLE (
    	[ProductAssemblyID] [int] NULL,
    	[ComponentID] [int] NULL,
    	[ComponentDesc] [nvarchar](50) NULL,
    	[TotalQuantity] [numeric](38, 2) NULL,
    	[StandardCost] [money] NULL,
    	[ListPrice] [money] NULL,
    	[BOMLevel] [smallint] NULL,
    	[RecursionLevel] [int] NULL)
        INSERT INTO @OMEGA 
        EXECUTE [AdventureWorks].[dbo].[uspGetBillOfMaterials] 800, '2004-02-01'
        SELECT * 
        FROM   @OMEGA; 
      END 
    
    GO 
    
    INSERT INTO Alpha 
    EXEC sprocINSERTEXEC 
    GO 
    /*
    Msg 8164, Level 16, State 1, Procedure sprocINSERTEXEC, Line 27
    An INSERT EXEC statement cannot be nested.
    
    (0 row(s) affected)
    */
    
    /****** WORKAROUND 1 OPENQUERY ************/
    
    INSERT INTO Alpha
    SELECT * FROM OPENQUERY (DELLSTAR, 'EXEC tempdb.dbo.sprocINSERTEXEC')
    GO
    -- (87 row(s) affected)
    
    /****** WORKAROUND 2 REMOVE INSERT SELECT from sproc *********/
    ALTER PROC sprocINSERTEXEC 
    AS 
      BEGIN 
        EXECUTE [AdventureWorks].[dbo].[uspGetBillOfMaterials] 800, '2004-02-01'
      END 
    GO 
    
    INSERT INTO Alpha 
    EXEC sprocINSERTEXEC 
    GO 
    -- (87 row(s) affected)
    
    /****** WORKAROUND 3 bcp - BULK INSERT round trip to file system ************/
    
    ALTER PROC sprocINSERTEXEC 
    AS 
      BEGIN 
        SET NoCount  ON 
       CREATE TABLE #OMEGA  (
    	[ProductAssemblyID] [int] NULL,
    	[ComponentID] [int] NULL,
    	[ComponentDesc] [nvarchar](50) NULL,
    	[TotalQuantity] [numeric](38, 2) NULL,
    	[StandardCost] [money] NULL,
    	[ListPrice] [money] NULL,
    	[BOMLevel] [smallint] NULL,
    	[RecursionLevel] [int] NULL)
        DECLARE @Command nvarchar(256)
        SET @Command = 'bcp "EXECUTE [AdventureWorks].[dbo].[uspGetBillOfMaterials] 800, ''2004-02-01'' " queryout "f:tempbom1.txt" -T -c'
        EXEC xp_cmdshell @Command, NO_OUTPUT
        BULK INSERT #OMEGA
        FROM 'f:tempbom1.txt'
        -- (87 row(s) affected)
        SELECT * 
        FROM   #OMEGA; 
      END 
    GO 
    
    INSERT INTO Alpha 
    EXEC sprocINSERTEXEC 
    GO 
    -- (87 row(s) affected)
    
    -- Cleanup
    DROP TABLE Alpha
    DROP PROC sprocINSERTEXEC 

    Kalman Toth SQL SERVER 2012 & BI TRAINING
    New Book: Beginner Database Design & SQL Programming Using Microsoft SQL Server 2012

    • Edited by
      Kalman Toth
      Thursday, September 27, 2012 9:19 PM
    • Marked as answer by
      Kalman Toth
      Thursday, September 27, 2012 9:20 PM

If you use the tSQLt unit testing framework for long enough, you will eventually run into the “An INSERT EXEC statement cannot be nested” problem. This problem happens when you try to insert the output of a stored procedure into a temporary table for validation, but that stored procedure internally already does the same thing, inserting the output of another stored procedure into a temporary table. SQL Server does not allow this.

Researching the problem will lead you to a lot of articles and posts in which people say that you must change your code, and for example use:

  • Table Valued Functions
  • Shared temporary tables
  • Table Variables as output parameters
  • Custom parameters on your stored procedures that are only applicable for testing
  • Workarounds using OPENROWSET, Linked Servers or even xp_cmdshell+sqlcmd

All those solutions are hacky, and it is simply not always possible to change the underlying code. Nor should you. Code should be tested exactly how it will be executed and used in the wild, else your test is not worth much and you’ll soon have users, or worse your boss, at your desk complaining about world ending errors.

In your search for a solution, you hopefully found an article by Charles Silbergleith called tSQLt and the INSERT EXEC Problem. It is a good article. You should read it. Charles’s solution is to use a CLR. If you’ve never used a CLR before, it is just a .Net application (dll) that is imported into SQL Server and executed like a stored procedure. By using a CLR, we can execute any queries or stored procedures as if the code is being run by an external application, like it would in the real world, and capture output of the query into a temporary table for validation. But Charles’s CLR implementation is far too advanced. It is going to take hours to figure out and even longer to get it working. This article is a follow on to Charles’s work, a quick and simple CLR How-to specific to tSQLt unit testing.

In this article, we’ll setup a test scenario that throws the “An INSERT EXEC statement cannot be nested” error and then step by step build a CLR to fix it. With lots of screenshots to help you along the way. I promise, you do not need to know how to code in C#. Just follow the steps below and copy paste.

Right, let’s get started.

The Test Scenario

Let’s say for example you have a stored procedure called “ComplexBusinessCalc” which you would like to unit test. Internally your stored procedure uses other stored procedures that you absolutely cannot change. And your stored procedure works perfectly as-is when you execute it.

e.g.

If you’d like to follow along and test out the CLR for yourself, you could use the following code to create the ComplexBusinessCalc stored procedure in your test environment.

create or alter procedure SomeReallyComplexProc
as
begin
    select 'hello' as val1;
end
go

create or alter procedure ComplexBusinessCalc
as
begin
    create table #tmp (val1 varchar(50));

    insert into #tmp (val1)
    exec SomeReallyComplexProc;

    update #tmp set val1 += ' world';

    select val1 from #tmp;

    drop table if exists #tmp;
end
go

For your unit test, all you want to do is run the ComplexBusinessCalc stored procedure and capture its output into a temporary table for validation.

Hint: if you have not used tSQLt before, here is a good place to start.

Your tSQLt unit test could look something like this. In the test below, we are simply checking that our ComplexBusinessCalc stored procedure returns at least one row of data.

exec tSQLt.NewTestClass 'demo';
go

create or alter procedure demo.[test that ComplexBusinessCalc returns data]
as
begin
    create table #test (val1 varchar(50));

    insert into #test (val1)
    exec ComplexBusinessCalc;

    if not exists (select * from #test)
    begin
        exec tSQLt.Fail 'No data returned';
    end
    
    drop table if exists #test;
end
go

But when you run your unit tests, you get stuck on the annoying “An INSERT EXEC statement cannot be nested” problem.

exec tSQLt.RunAll;

Result

[demo].[test that ComplexBusinessCalc returns data] failed: (Error) Message: An INSERT EXEC statement cannot be nested. | Procedure: ComplexBusinessCalc (6) | Severity, State: 16, 1 | Number: 8164

The solution is simple. All you need to do is change one line in your unit test. Instead of executing the ComplexBusinessCalc stored procedure within the current transaction’s context, you instead pass it as a query to a CLR to execute on it a new connection not enlisted in the same transaction context as the tSQLt test. See the «exec Query» line which replaced «exec ComplexBusinessCalc». The «Query» object looks like a stored procedure, and it is, but underneath it calls a CLR.

create or alter procedure demo.[test that ComplexBusinessCalc returns data]
as
begin
    create table #test (val1 varchar(50))

    insert into #test (val1)
    exec Query @server = @@servername, @query = 'exec Demo..ComplexBusinessCalc';

    if not exists (select * from #test)
    begin
        exec tSQLt.Fail 'No data returned';
    end

    drop table if exists #test;
end
go

Hint: If you need to pre-prepare some specific data for the unit test, add those commands into the @query parameter. The SQL connection made by the CLR would not be able to read any data setup via the tSQLt unit test’s transaction unless you allow uncommitted reads. Remember to rollback afterwards.

But, we first need to go create that “Query” CLR that accepts T-SQL commands and executes those against your SQL instance as an external application on a different transaction.

Here we go. Get ready to copy paste.

Create your first CLR

Step 1: Run Visual Studio and ensure that you have the “Data storage and processing” feature set installed. You can download the Visual Studio Community Edition for FREE from https://visualstudio.microsoft.com/ . All the screenshots in this article are from Visual Studio 2022.

In Visual Studio, via the Tools menu, open the “Get Tools and Features” dialog and ensure the Data storage and processing features are installed.

Step 2: Create a new SQL Server Database Project. You can access the Create Project dialog via the Files > New Project menu.

Step 3: Add a “SQL CLR C# Stored Procedure” item to your database project, by right clicking on your project inside the Solution Explorer view and selecting Add > New Item from the context menu. This will open the Add New Item dialog. Select the “SQL CLR C# Stored Procedure” template.

Name the new item Query.cs

The default template code inside Query.cs will look like this:

Replace all the default template code inside Query.cs with the following. Note, if you do change the SqlConnection, you must remember to keep the setting Enlist=false else the connection pooler will automatically enlist the connection in the creation thread’s current transaction context, and you’ll get the same “An INSERT EXEC statement cannot be nested” error, even when running your stored procedure via the CLR.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class Query
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void Exec(SqlString server, SqlString query)
    {
        SqlConnectionStringBuilder sb = new SqlConnectionStringBuilder
        {
            DataSource = server.ToString(),
            IntegratedSecurity = true,
            Enlist = false
        };

        using (SqlConnection conn = new SqlConnection(sb.ToString()))
        {
            conn.Open();
            SqlCommand cmd = new SqlCommand(query.ToString(), conn);
            SqlDataReader reader = cmd.ExecuteReader();
            SqlContext.Pipe.Send(reader);
        }
    }
}

Step 4: Select the “Release” solution configuration from the drop down in the top toolbar in Visual Studio.

Step 5: Make the “Output” window visible in Visual Studio via the View menu.

The Output window will pop up and look like this. After you have successfully built your database project, the name and path to your new .dll will be displayed in this window.

Step 6: Build your database project by right clicking on it in the Solution Explorer view and selecting “Build” from the context menu.

The Output window will show that your project was successfully built and the full name and path to your newly created CLR.

Step 7: Import your new CLR dll into SQL

exec sp_configure 'clr enabled', 1;  
reconfigure;  
go

alter database Demo set trustworthy on; --change to your database name
go

use Demo --change to your database name
go
 --change path to dll
create assembly ExternalUtils from 'C:UsersbrinksourcereposDatabase2Database2binReleaseDatabase2.dll'
with permission_set = external_access;
go

create procedure Query
@server nvarchar(max)
, @query nvarchar(max)
as
external name ExternalUtils.Query.[Exec];
go

Step 8: Test your newly created CLR. By the way, if you haven’t noticed, the CLR uses Windows Authentication (a.k.a IntegratedSecurity). You’ll have to change the SqlConnection code in the CLR if you only have SQL Authentication in your environment.

Done: That’s all folks. Now simply update your unit test to use the CLR to execute your stored procedure. After which your tSQLt unit test will run perfectly.

An INSERT EXEC statement cannot be nested

«INSERT EXEC statement cannot be nested» problem fixed using a CLR

Bonus Tip

You can use the “Generate Scripts…” task in SQL Server Management Studio (SSMS) to export your CLR as a T-SQL script. This makes deployment of the CLR to other servers very easy.

The output script will look something like this. Simply include it as-is in your deployment patches.

Have fun unit testing.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Отидез форте инструкция по применению в ветеринарии для кошек
  • Scarlett vita spa весы напольные инструкция по применению
  • Сборка пластиковых окон своими руками в домашних условиях пошаговая инструкция
  • Как провести котировку по 44 фз пошаговая инструкция
  • Мед препарат фезам инструкция по применению