"INSERT EXEC statement cannot be nested" with dynamic query - sql-server

BACKGROUND
I have a procedure that has INSERT INTO ... EXEC (#sql) from dynamic SQL.
This procedure's results is INSERTed INTO table outside of procedure. When I attempt this, I get get an error:
[S0001][8164] An INSERT EXEC statement cannot be nested.
This error is discussed in other questions, but for inner procedures' calls instead of dynamic SQL:
Errors: "INSERT EXEC statement cannot be nested." and "Cannot use the ROLLBACK statement within an INSERT-EXEC statement." How to solve this?
An insert exec statement cannot be nested
Example with error:
-- =================================
-- table with test data
-- =================================
CREATE TABLE dbo.TestInsertIntoDynamicData1
(
data nvarchar(max)
)
INSERT INTO dbo.TestInsertIntoDynamicData1
VALUES ('one1'), ('two1'), ('three1')
GO
-- =================================
-- another table with test data
-- =================================
CREATE TABLE dbo.TestInsertIntoDynamicData2
(
data nvarchar(max)
)
INSERT INTO dbo.TestInsertIntoDynamicData2
VALUES ('one2'), ('two2'), ('three2')
GO
-- =================================
-- procedure with dynamic query
-- =================================
CREATE PROCEDURE dbo.TestInsertIntoDynamicProc
#TableName nvarchar(100)
AS
BEGIN
DECLARE #Results table(
data nvarchar(max)
)
DECLARE #sql nvarchar(max)
SET #sql = '
SELECT data
FROM dbo.' + #TableName + ';
'
-- FIRST INSERT INTO ... EXEC ...
INSERT INTO #Results -- this INSERT is required for example
EXEC (#sql)
SELECT *
FROM #Results;
END
GO
-- =================================
-- CALL
-- =================================
DECLARE #ResultsOfProc table(
data nvarchar(max)
)
-- SECOND INSERT INTO ... EXEC ...
INSERT INTO #ResultsOfProc (data)
EXEC dbo.TestInsertIntoDynamicProc #TableName = 'TestInsertIntoDynamicData2'
SELECT *
FROM #ResultsOfProc;
GO
DROP TABLE dbo.TestInsertIntoDynamicData1
DROP TABLE dbo.TestInsertIntoDynamicData2
DROP PROCEDURE dbo.TestInsertIntoDynamicProc
https://stackoverflow.com/a/2917775/7573844
QUESTION
How can we get around this error?

Move INSERT INTO to dynamic query.
Refactor table variable to temp table in order to use it in dynamic query.
Fixed procedure:
CREATE PROCEDURE dbo.TestInsertIntoDynamicProcFixed
#TableName nvarchar(100)
AS
BEGIN
CREATE TABLE #Results ( -- refactor to temp table
data nvarchar(max)
)
DECLARE #sql nvarchar(max)
SET #sql = '
INSERT INTO #Results -- move INSERT to here
SELECT data
FROM dbo.' + #TableName + ';
'
-- FIRST INSERT INTO ... EXEC ...
EXEC (#sql)
SELECT *
FROM #Results;
END
GO

Related

Create temp table from dynamic SQL with parameters

Here is my issue. I need to create a temp table after executing dynamic SQL and passing params as follows
CREATE PROCEDURE SP1
#param1 varchar(50),
#param2 varchar(50)
AS
BEGIN
DECLARE #PDef varchar(300)
DECLARE #sql varchar(300)
DECLARE #localparam1 varchar(300)
DECLARE #localparam2 varchar(300)
SET #localparam1 = ....
SET #localparam2 = ....
SET #PDef = '#param1 varchar(50), #localparam1 varchar(300)'
SET #sql = 'SELECT * FROM TABL1 WHERE COL1 = #param1, COL2 in (#localparam1)'
EXEC sp_Executesql #sql, #PDef,
#param1 = #param1, #localparam1 = #localparam1
The above works. How do I get the results into a temp table?
I tried
CREATE TABLE #T1 (col1 varchar(50), col2 varchar(50) )
INSERT INTO #T1
EXECUTE #sql -- didn't work
INSERT INTO #T1
EXECUTE (#sql, #PDef, #param1 = #param1, #localparam1 = #localparam1) -- didn't work either
EDIT: Had Looked at the following samples while using EXECUTE
Dynamic SQL results into temp table in SQL Stored procedure and hence used EXECUTE
The accepted answer was:
INSERT into #T1 execute ('execute ' + #SQLString )
omit the 'execute' if the sql string is something other than a procedure
Now see the comments to that accepted answer that question that accepted answer :-)
You've removed sp_executesql from your query for some reason. You can't called sys.sp_executesql if you don't tell SQL Server to:
INSERT INTO #T1
EXECUTE sys.sp_executesql #sql;
INSERT INTO #T1
EXECUTE sys.sp_executesql #sql,
#PDef,
#param1 = #param1,
#localparam1 = #localparam1;

Executing T-SQL query from table

I have the following stored procedure:
CREATE PROCEDURE MergeTable #TableName VARCHAR(256)
AS
BEGIN
DECLARE #Test VARCHAR(max)
SET #Test = (SELECT Query
FROM dbo.QueryMergeDWH
WHERE SourceTableName = #TableName)
EXEC #Test
END
GO
EXEC MergeTable #TableName = 'SGPREINVOICE'
The table dbo.QueryMergeDWH holds queries for different tables I want to run using the stored procedure. The variable #Test holds the query (a MERGE statement) for the table 'SGPREINVOICE' in this case.
However, when I run this code I get the following error:
Msg 203, Level 16, State 2, Procedure MergeTable, Line 11 [Batch Start Line 19]
The name 'MERGE dwh.SGPREINVOICE t
USING stg.SGPREINVOICE s
ON t.VOUCHER = s.VOUCHER
WHEN NOT MATCHED
THEN INSERT VALUES (BATCHNAME, COMPANYNR, BUDGETHOUDER, VENDORNR, BANKACCOUNTID, [DESCRIPTION], EXTERNALINVOICENR, TAXVERLEGD, INVOICEDATE, BOOKINGDATE, INVOICEAMOUNTDEB, INVOICEAMOUNTCRED, SGAMOUNTHIGH, SGAMOUNTLOW, SGAMOUNTNOTAX, SGTAXAMOUNTLOW, SGTAXAMOUNTHIGH, SGTAXTTV, G_ACCOUNT, G_AMOUNT, INVOICETYPE, SGAUTOINCASSO, SGIMPORTPROCESSID, SGINVOICEBLOCKED, SGCOMPANYNRORIG, SGVENDORNRORIG, SGBANKACCOUNTIDORIG, SGG_ACCOUNTORIG, SGPOSTBUSNR, SGADDRESS, SGHUISNR, SGHUISNRTOEV, SGPOSTCODE, SGWOONPL, SGBTWNR, SGBEDRIJFSNM, SGINVOICEAMOUNTORIG, V' is not a valid identifier.
It seems like the variable #Test does not contain the entire statement but only part of it. How can I solve this problem?
The actual reason for this error is the difference between EXEC #Test (to execute a stored procedure or function) and EXEC (#Test) (to execute a dynamic statement). Note, that you may use sp_executesql to execute a dynamically generated statement:
CREATE PROCEDURE MergeTable #TableName VARCHAR(256)
AS
BEGIN
DECLARE #Test VARCHAR(max)
SELECT #Test = Query
FROM dbo.QueryMergeDWH
WHERE SourceTableName = #TableName
EXEC (#Test)
-- Or using sp_executesql
-- DECLARE #err int
-- EXEC #err = sp_executesql #Test
-- IF #err <> 0 PRINT 'Error'
END
GO
EXEC MergeTable #TableName = 'SGPREINVOICE'

Need help. Message says temp table doesn't exist: 'Invalid object name '#TempCodes'.'

If I run the select statement below by itself (with the table name hard coded in) it runs fine and the temp table is created. If I run it as below it says 'Invalid object name '#TempCodes'' although when I print #Sql1 it looks exactly the same as when I run it by itself (with the table name hard coded in).
Any ideas on what's going on here will be appreciated.
DECLARE #TableName NVARCHAR(50)
SET #TableName = '[My Table Name]'
DECLARE #Sql1 NVARCHAR(MAX);
SET #Sql1 = N'SELECT AccountNumber,LTRIM(RTRIM(m.n.value(''.[1]'',''varchar(8000)''))) AS mdcodes INTO #TempCodes FROM (SELECT AccountNumber,CAST(''<XMLRoot><RowData>''
+ REPLACE(MD_Results,'','',''</RowData><RowData>'')
+ ''</RowData></XMLRoot>'' AS XML) AS x FROM ' + #TableName
+ N')t CROSS APPLY x.nodes(''/XMLRoot/RowData'')m(n)'
IF OBJECT_ID('tempdb.dbo.#TempCodes', 'U') IS NOT NULL
BEGIN
drop table #TempCodes
END
EXECUTE sp_executesql #Sql1
Select * from #TempCodes
I believe ## is a typo. Even if you fix it to # it will throw same error.
EXECUTE sp_executesql #Sql1
A local temporary table created in a stored procedure is dropped
automatically when the stored procedure is finished.
In your case sp_executesql is the stored procedure.
the table created inside the dynamic query will be dropped after the exec sp_executesql is completed. That's why you are getting that error.
You need to create table outside and use Insert into table..select syntax inside the dynamic query
IF OBJECT_ID('tempdb.dbo.#TempCodes', 'U') IS NOT NULL
drop table #TempCodes
create table #TempCodes(AccountNumber varchar(100),mdcodes varchar(100))
SET #Sql1 = N'Insert into #TempCodes
SELECT AccountNumber,LTRIM(RTRIM(m.n.value(''.
[1]'',''varchar(8000)''))) AS mdcodes FROM (SELECT
AccountNumber,CAST(''<XMLRoot><RowData>'' +
REPLACE(MD_Results,'','',''</RowData><RowData>'') + ''</RowData></XMLRoot>''
AS XML) AS x FROM '+#TableName+ N')t CROSS APPLY
x.nodes(''/XMLRoot/RowData'')m(n)'
Try using a global temp table ##TempCodes as local temporary tables are only visible in current session.
DECLARE #TableName NVARCHAR(50)
SET #TableName = '[My Table Name]'
DECLARE #Sql1 NVARCHAR(MAX);
SET #Sql1 = N'SELECT AccountNumber,LTRIM(RTRIM(m.n.value(''.
[1]'',''varchar(8000)''))) AS mdcodes INTO ##TempCodes FROM (SELECT
AccountNumber,CAST(''<XMLRoot><RowData>'' +
REPLACE(MD_Results,'','',''</RowData><RowData>'') + ''</RowData></XMLRoot>''
AS XML) AS x FROM '+#TableName+ N')t CROSS APPLY
x.nodes(''/XMLRoot/RowData'')m(n)'
IF OBJECT_ID('tempdb.dbo.##TempCodes', 'U') IS NOT NULL
BEGIN
drop table ##TempCodes
END
EXECUTE sp_executesql #Sql1
Select * from ##TempCodes

SQL Server declare and execute query for temp table

I have a temp table like
DECLARE #t TABLE (a BIGINT)
I want to do this
SELECT * FROM #t
but I need to do it by EXEC query, and this doesn't work
DECLARE #query AS NVARCHAR(100) = 'SELECT * FROM #t'
EXEC(#query)
how can I create a custom query to select the temp table?
Thanks.
This is because EXEC statement will execute the Statements in new Session. And the Table variables scope is fixed to the batch of statement.
Since you declared the Table Variable out side of the Session , you can't access the table variable in EXEC statement.
So you need to DECLARE ,INSERT, UPDATE, SELECT table variables in the Dynamic Code itself.
DECLARE #query NVARCHAR(MAX)='';
SELECT #query ='
DECLARE #t TABLE
(
a BIGINT
)'
SELECT #query += 'SELECT * FROM #t'
EXEC(#query)
Solution 2:
The another Solution is to create Global Temporary Table which we can create using ##. And Global Temporary tables scope is limited to Database all connections.
CREATE TABLE ##TABLE1
(
a BIGINT
)
DECLARE #query NVARCHAR(MAX)='';
SELECT #query += 'SELECT * FROM ##TABLE1'
EXEC(#query)
But be aware if another user execute the same Query, there might be conflict with the same name creation.
Try hash table as below:
DECLARE #tblTest AS Table
(
Name VARCHAR(50)
)
insert into #tblTest values('Sandip')
insert into #tblTest values('AAA')
IF OBJECT_ID('tempdb..#tblTest') IS NOT NULL
DROP TABLE #tblTest
SELECT * INTO #tblTest FROM #tblTest
DECLARE #query AS NVARCHAR(100) = 'SELECT * FROM #tblTest'
EXEC (#query)
Output:
CREATE TYPE MyTable AS TABLE
(
a BIGINT
);
GO
DECLARE #T AS MyTable;
EXEC sp_executesql
N'SELECT * FROM #T',
N'#T MyTable READONLY',
#T=#T
please try this

Dynamic SQL results into temp table in SQL Stored procedure

The code is as follows:
ALTER PROCEDURE dbo.pdpd_DynamicCall
#SQLString varchar(4096) = null
AS
Begin
create TABLE #T1 ( column_1 varchar(10) , column_2 varchar(100) )
insert into #T1
execute ('execute ' + #SQLString )
select * from #T1
End
The problem is that I want to call different procedures that can give back different columns.
Therefore I would have to define the table #T1 generically.
But I don't know how.
Can anyone help me on this problem?
Try:
SELECT into #T1 execute ('execute ' + #SQLString )
And this smells real bad like an sql injection vulnerability.
correction (per #CarpeDiem's comment):
INSERT into #T1 execute ('execute ' + #SQLString )
also, omit the 'execute' if the sql string is something other than a procedure
You can define a table dynamically just as you are inserting into it dynamically, but the problem is with the scope of temp tables. For example, this code:
DECLARE #sql varchar(max)
SET #sql = 'CREATE TABLE #T1 (Col1 varchar(20))'
EXEC(#sql)
INSERT INTO #T1 (Col1) VALUES ('This will not work.')
SELECT * FROM #T1
will return with the error "Invalid object name '#T1'." This is because the temp table #T1 is created at a "lower level" than the block of executing code. In order to fix, use a global temp table:
DECLARE #sql varchar(max)
SET #sql = 'CREATE TABLE ##T1 (Col1 varchar(20))'
EXEC(#sql)
INSERT INTO ##T1 (Col1) VALUES ('This will work.')
SELECT * FROM ##T1
Hope this helps,
Jesse
Be careful of a global temp table solution as this may fail if two users use the same routine at the same time as a global temp table can be seen by all users...
create a global temp table with a GUID in the name dynamically. Then you can work with it in your code, via dyn sql, without worry that another process calling same sproc will use it. This is useful when you dont know what to expect from the underlying selected table each time it runs so you cannot created a temp table explicitly beforehand. ie - you need to use SELECT * INTO syntax
DECLARE #TmpGlobalTable varchar(255) = 'SomeText_' + convert(varchar(36),NEWID())
-- select #TmpGlobalTable
-- build query
SET #Sql =
'SELECT * INTO [##' + #TmpGlobalTable + '] FROM SomeTable'
EXEC (#Sql)
EXEC ('SELECT * FROM [##' + #TmpGlobalTable + '] ')
EXEC ('DROP TABLE [##' + #TmpGlobalTable + ']')
PRINT 'Dropped Table ' + #TmpGlobalTable
INSERT INTO #TempTable
EXEC(#SelectStatement)
Try Below code for creating temp table dynamically from Stored Procedure Output using T-SQL
declare #ExecutionName varchar(1000) = 'exec [spname] param1,param2 '
declare #sqlStr varchar(max) = ''
declare #tempTableDef nvarchar(max) =
(
SELECT distinct
STUFF(
(
SELECT ','+a.[name]+' '+[system_type_name]
+'
' AS [text()]
FROM sys.dm_exec_describe_first_result_set (#ExecutionName, null, 0) a
ORDER BY a.column_ordinal
FOR XML PATH ('')
), 1, 1, '') tempTableDef
FROM sys.dm_exec_describe_first_result_set (#ExecutionName, null, 0) b
)
IF ISNULL(#tempTableDef ,'') = '' RAISERROR( 'Invalid SP Configuration. At least one column is required in Select list of SP output.',16,1) ;
set #tempTableDef='CREATE TABLE #ResultDef
(
' + REPLACE(#tempTableDef,'
','') +'
)
INSERT INTO #ResultDef
' + #ExecutionName
Select #sqlStr = #tempTableDef +' Select * from #ResultDef '
exec(#sqlStr)
DECLARE #EmpGroup INT =3 ,
#IsActive BIT=1
DECLARE #tblEmpMaster AS TABLE
(EmpCode VARCHAR(20),EmpName VARCHAR(50),EmpAddress VARCHAR(500))
INSERT INTO #tblEmpMaster EXECUTE SPGetEmpList #EmpGroup,#IsActive
SELECT * FROM #tblEmpMaster
CREATE PROCEDURE dbo.pdpd_DynamicCall
AS
DECLARE #SQLString_2 NVARCHAR(4000)
SET NOCOUNT ON
Begin
--- Create global temp table
CREATE TABLE ##T1 ( column_1 varchar(10) , column_2 varchar(100) )
SELECT #SQLString_2 = 'INSERT INTO ##T1( column_1, column_2) SELECT column_1 = "123", column_2 = "MUHAMMAD IMRON"'
SELECT #SQLString_2 = REPLACE(#SQLString_2, '"', '''')
EXEC SP_EXECUTESQL #SQLString_2
--- Test Display records
SELECT * FROM ##T1
--- Drop global temp table
IF OBJECT_ID('tempdb..##T1','u') IS NOT NULL
DROP TABLE ##T1
End
Not sure if I understand well, but maybe you could form the CREATE statement inside a string, then execute that String? That way you could add as many columns as you want.

Resources