I have a new stored procedure that contain of following script:
Insert into #tmpTable
Exec dbo.SP_GetData '2016-11-01'
and above script causes a SQL Server error message
An INSERT EXEC statement cannot be nested.
because there is another INSERT EXEC inside the SP_GetData stored procedure and according to a few hours of reading on this error message, I can't have two INSERT EXEC or I will get that error.
I found several alternatives such as using inline query to insert into temp table and modify the existing SP_GetData stored procedure but both way can't work for me. In my case, it's a must to depend on SP_GetData as it involve many complicated process and must not be changed.
I just need to retrieve all data from the SP_GetData in my new stored procedure and return.
Please let me know what is my alternatives on this.
DECLARE #Query VARCHAR(100),#Date VARCHAR(100)
SELECT #Date = '2016-11-01'
SELECT #Query = 'dbo.SP_GetData '''+#Date+''''
INSERT INTO #tmpTable
EXEC (#Query)
Related
I have a stored procedure, usp_region and it has a select statement with 50 columns as the result set. This procedure is called by multiple other stored procedures in our application.
Most of the stored procedure pass a parameter to this procedure and display the result set that it returns. I have one stored procedure, usp_calculatedDisplay, that gets the columns from this stored procedure and inserts the values into a temp table and does some more calculations on the columns.
Here's a part of the code in usp_calculatedDisplay.
Begin Procedure
/* some sql statements */
Declare #tmptable
(
-- all the 50 columns that are returned from the usp_region procedure
)
Insert Into #tmptable
exec usp_region #regionId = #id
Select t.*, /* a few calculated columns here */
From #tmptable t
End of procedure
Every time I add a column to the usp_region procedure, I'll also have to make sure I have to add it to this procedure. Otherwise it breaks. It has become difficult to maintain it since it is highly possible for someone to miss adding a column to the usp_calculatedDisplay procedure when the column is added to the usp_region.
In order to overcome this problem, I decided to do this:
Select *
Into #tmptable
From OPENROWSET('SQLNCLI',
'Server=localhost;Trusted_Connection=yes;',
'EXEC [dbo].[usp_region]')
The problem is 'Ad Hoc Distributed Queries' component is turned off. So I can't use this approach to overcome this issue. I was wondering if there are any other ways of overcoming this problem. I would really appreciate any help. Thank you!
Every time I add a column to the usp_region procedure
SQL Server is a structured database and it does not meant to solve such cases that you need to change your structure every day.
If you add/remove columns so often then you probably did not choose the right type of database, and you better re-design your system.
It has become difficult to maintain it since it is highly possible for someone to miss adding a column to the usp_calculatedDisplay procedure when the column is added to the usp_region.
There are two simple solutions for this (1) using DDL Triggers - very bad idea but simple to implement and working. (2) Using my trick to select from stored procedure
Option 1: using DDL trigger
You can automate the entire procedure and ALTER the stored procedure usp_calculatedDisplay every time that the stored procedure usp_region is changed
https://learn.microsoft.com/en-us/sql/relational-databases/triggers/ddl-triggers
The basic approach is
CREATE OR ALTER TRIGGER NotGoodSolutionTrig ON DATABASE FOR ALTER_PROCEDURE AS BEGIN
DECLARE #var_xml XML = EVENTDATA();
IF(
#var_xml.value('(EVENT_INSTANCE/DatabaseName)[1]', 'sysname') = 'tempdb'
and
#var_xml.value('(EVENT_INSTANCE/SchemaName)[1]', 'sysname') = 'dbo'
and
#var_xml.value('(EVENT_INSTANCE/ObjectName)[1]', 'sysname') = 'usp_region'
)
BEGIN
-- Here you can parse the text of the stored procedure
-- and execute ALTER on the first SP
-- To make it simpler, you can design the procedure usp_region so the columns names will be in specific row or between to comment which will help us to find it
-- The code of the Stored Procedure which you need to parse is in the value of:
-- #var_xml.value('(EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)'))
-- For example we can print it
DECLARE #SP_Code NVARCHAR(MAX)
SET #SP_Code = CONVERT(NVARCHAR(MAX), #var_xml.value('(EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)'))
PRINT #SP_Code
-- In your case, you need to execute ALTER on the usp_calculatedDisplay procedure using the text from usp_region
END
END
Option 2: trick to select from stored procedure using sys.dm_exec_describe_first_result_set
This is simple and direct way to get what you need.
CREATE OR ALTER PROCEDURE usp_calculatedDisplay AS
-- Option: using simple table, so it will exists outsie the scope of the dynamic query
DROP TABLE IF EXISTS MyTable;
DECLARE #sqlCommand NVARCHAR(MAX)
select #sqlCommand = 'CREATE TABLE MyTable(' + STRING_AGG ([name] + ' ' + system_type_name, ',') + ');'
from sys.dm_exec_describe_first_result_set (N'EXEC usp_region', null,0)
PRINT #sqlCommand
EXECUTE sp_executesql #sqlCommand
INSERT MyTable EXECUTE usp_region;
SELECT * FROM MyTable;
GO
Note!!! Both solutions are not recommended in production. My advice is to avoid such needs by redesign your system. If you need to re-write 20 SP so do it and don't be lazy! Your goal should be what best for the database usage.
I have a user-defined table type tyAnalysisNumbers. I need to populate my user defined data type within a stored procedure with a SELECT statement and I am struggling to get that working within my stored procedure.
The following ways I have tried do not work
DECLARE #MyTable tyAnalysisNumbers;
INSERT INTO #MyTable
EXEC ('SELECT * FROM ' + #someTable);
I get this error:
An INSERT EXEC statement cannot be nested
I am unsure how to insert into my custom table via a select statement.
Can anyone help me accomplish this?
An INSERT EXEC statement cannot be nested
Above error is self explanatory. Please look at below scenario:
For example, we have one procedure which inserts data in table type and return result.
CREATE PROCEDURE uspInsertData1
AS
BEGIN
DECLARE #MyTable tyAnalysisNumbers;
INSERT INTO #MyTable
EXEC ('SELECT * FROM someTable');
select * from #MyTable
END
Now, let's say we have another procedure which will call above procedure and again insert data in another table.
CREATE PROCEDURE uspInsertData2
AS
BEGIN
DECLARE #MyTable tyAnalysisNumbers;
INSERT INTO sometable
EXEC uspInsertData1
END
Now, if you execute 1st procedure it will work fine but if you execute second procedure you will get this error.
An INSERT EXEC statement cannot be nested.
Because now you have nested EXEC statements.
I suggest to finish your work in single stored procedure if possible.
Try it like this:
DECLARE #MyTable tyAnalysisNumbers;
SELECT * INTO #Temp FROM #MyTable;
DECLARE #tblName AS SYSNAME = (SELECT name FROM sys.tables WHERE name = #someTable);
EXEC ('INSERT INTO #Temp SELECT * FROM ' + #tblName);
This also addresses the SQL Injection problem.
I have 2 stored procedures which returns same but unknown columns. I need to write a proc to combine results from both stored procedures. I tried OPENROWSET but problem is to provide the connection string in OPENROWSET function, even if I specify the connection string one time, it will be different for different environments and I think that will be the worst thing to change connection string each time I deploy the application in different environments or if the user is changed on server. Can someone help me to get this done in the best way.
I cannot write them as function since the procs are using temp tables.
Declare #connection nvarchar(200)
Declare #sql nvarchar(max)
Set #connection= 'Server=servername;initial
catalog=dbname;user=abc,password=xyz';
Set #sql='SELECT * INTO #temp1
FROM OPENROWSET(
''SQLNCLI'',
'''+ #connection + ''',
''EXEC sp_name '')'
Exec(#sql)
--- creating a temporary table
CREATE Table #Dynamic_Temp_Table (_field_only_for_create_ INT )
--- Addition of fields from the first recordset from the first procedure
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL=ISNULL(#SQL+',','ALTER TABLE #Dynamic_Temp_Table ADD ')+name+' '+system_type_name
FROM sys.dm_exec_describe_first_result_set('exec sp_proc_first', NULL, NULL)
order by column_ordinal
exec sp_executesql #SQL
--- Remove of the first unused column
ALTER TABLE #Dynamic_Temp_Table drop column _field_only_for_create_
--- Addition of the result from the first procedure
INSERT INTO #Dynamic_Temp_Table
exec sp_proc_first
--- Addition of the result from the second procedure
INSERT INTO #Dynamic_Temp_Table
exec sp_proc_second
--- result: exec sp_proc_first UNION ALL exec sp_proc_second
select * from #Dynamic_Temp_Table
--- result: exec sp_proc_first UNION exec sp_proc_second
select DISTINCT * from #Dynamic_Temp_Table
It is possible but not easy at all....
You can change your stored procedures to just create and populate a global temp tables (no select), then you can select both with an union.
To use OPENROWSET as you are doing it in your current approach you will need global temp table too, But as you mention that your stored procedures are using Temp Tables, OPENROWSET, OPENQUERY or sys.dm_exec_describe_first_result_set will not be able to determine the metadata to create the temp table.
Another option is to change your stored procedures to use variable tables instead of temp tables, then the metadata could be redetermined. #chrszcpl's answer https://stackoverflow.com/a/55632401/10932521 is a very good solution if you are able to do that.
If this is not possible (I assume that this isn't, otherwise the columns would't be unknown) because you are using dynamic sql in your procedures, or you simply can't touch those procedures for any reason, I think that the cheapest solution is create a third stored procedure which returns the dynamic columns definition that the other procedures will return..
In a stored procedure, I have an EXEC statement; I want to store its results into a table.
First I create table parameter as:
DECLARE #MyTable AS TABLE
(
[Item1] INT,
[Item2] DECIMAL
)
Then I try to insert values as:
INSERT INTO #MyTable
EXEC [storedProcedure] #testitem = #testitem
My question: is this the right way to do that? Or do I need to use a dynamic query?
Because I read similar questions like this and they suggest to use a dynamic query.
You have to use a dynamic query.
DECLARE #Sql AS VARCHAR(MAX);
SET #Sql='EXEC storedProcedure #testitem = #testitem'
INSERT INTO #MyTable ([Item1], [Item2]) EXEC(#Sql)
SELECT * FROM #MyTable;
Asserting the above, I've been just tempted to read further... and found this older thread. I bet, it is helpful to analyze it further. Insert results of a stored procedure into a temporary table
I have a stored procedure that produces a script of insert statements as result.
I want to execute those statements so that I can fill the data in my table
For example :
SET NOCOUNT ON
INSERT INTO table ([UID], [Name])
VALUES ('1000002', 'name'),
('1000004', 'name2')
The stored procedure return this as result, I want to execute this script how do I do it?
If I understand correctly, the above code is printed out when the stored procedure runs.
I would start by modifying the stored procedure to take an output parameter. Stored procedures "return" integer values, not messages. They happen to print things out, but it is not good to depend on that behavior.
Then, you can simply do:
declare #str nvarchar(max);
exec <stored procedure> #str output;
exec #str;
I suspect there might be a better way to structure the code. Having stored procedures return code should only be done when you really understand what you are doing. I don't think I've ever written code where a stored procedure would return an insert statement. It seems better to run dynamic SQL in the stored procedure itself.
EDIT:
You can capture the output of the stored procedure as well:
declare #t table (id int identity, line nvarchar(max));
insert into #t(line)
exec(<stored procedure>);
Then, you can concatenate the values. You can use XML for this purpose, but I think this will work:
declare #str nvarchar(max);
set #str = '';
select #str = #str + line + ' '
from #t
order by id;
exec #str;
Thanx All of you but following is the way i should have done
USE db1;
SELECT *
INTO [table]
FROM [db2].[dbo].[table]
it helps me to copy a table