I am trying to build an SQL inside a stored procedure and execute it using
EXEC sp_executesql
Now I defined a local table and tried to pass it in need to pass it in
CREATE TYPE mytabletypeAS TABLE (
StartDate DATETIME,
EndDate DATETIME,
Amount MONEY,
AccountId INT
);
The following happens in my stored procedure, what I am trying to do is to return the output produced by EXEC sp_executesql:
CREATE PROCEDURE attributevalues.sp_EvalClearingNetSpend
AS
BEGIN
DECLARE #OutPutTable AS mytabletype;
DECLARE #Sql AS NVARCHAR(MAX);
SET #Sql = 'INSERT INTO #OutPutTable SELECT StartDate,EndDate,Amount, AccountId FROM table1';
EXEC sp_executesql #Sql, N'#OutPutTable mytabletype OUTPUT', #OutPutTable OUTPUT;
SELECT * FROM #OutPutTable
END
The above is saying I cannot pass in OUTPUT with #OutPutTable
Help!!!
all i need to know is, is there a way I can get the values from a statement executed via EXEC and return it from my SP
Yes. But you can't do it with a table variable. You can pass a table variable into the nested batch with sp_executesql just like you pass one to a stored procedure, but it has to be marked readonly, and so you can't modify it.
You can see and modify existing temporary tables in nested batches, eg
drop table if exists table1
go
create table table1
(
StartDate DATETIME,
EndDate DATETIME,
Amount MONEY,
AccountId INT
)
insert into table1 values (getdate(),getdate(),1,1)
go
CREATE OR ALTER PROCEDURE EvalClearingNetSpend
AS
BEGIN
create table #t(
StartDate DATETIME,
EndDate DATETIME,
Amount MONEY,
AccountId INT)
Declare #Sql as NVARCHAR(MAX);
SET #Sql = 'INSERT INTO #T SELECT StartDate,EndDate,Amount, AccountId FROM table1';
EXEC sp_executesql #Sql
SELECT * FROM #T
END
go
exec EvalClearingNetSpend
outputs
StartDate EndDate Amount AccountId
----------------------- ----------------------- --------------------- -----------
2021-03-30 08:48:32.350 2021-03-30 08:48:32.350 1.00 1
This is an XY Problem. There is no need for the level of complexity you have put in the procedure. The table TYPE, the call to sys.sp_executesql, none of it is needed. Just put the SELECT statement of your "dynamic" query (it's not dynamic, as there's no object injection) in your Procedure:
CREATE PROCEDURE attributevalues.EvalClearingNetSpend AS --Removed sp_ prefix
BEGIN
SELECT StartDate,EndDate,Amount, AccountId FROM table1;
END;
GO
This completely avoids the error, that you are trying to use a table TYPE as an OUTPUT parameter, because you can't, but you don't need one here.
Not sure why you would go through the trouble of creating a Table Type parameter and populating it inside your from and then selecting from it. But lets say you do have to do this for some reason.
You can achieve this by doing this, no need to use output parameters at all, output parameter is used to return a scalar value, here you are getting a table back and then selecting from it:
CREATE PROCEDURE usp_EvalClearingNetSpend
AS
BEGIN
Declare #Sql as NVARCHAR(MAX);
SET #Sql = N' DECLARE #OutPutTable AS mytabletype;
INSERT INTO #OutPutTable
SELECT StartDate,EndDate,Amount, AccountId
FROM table1;
SELECT * FROM #OutPutTable
';
EXEC sp_executesql #Sql
END
Related
Does anyone know why this query doesn't work? How to add the table variable itemId as parameter to the exec statement? Thanks
DECLARE #test TABLE
(
itemId UNIQUEIDENTIFIER,
finalAmount DECIMAL
);
INSERT INTO #test EXEC [GetItems]
DECLARE #sql NVARCHAR(max)
DECLARE #param NVARCHAR(max)
SET #param = N'select itemId from #test'
SELECT #sql = 'EXEC [InsertTestItem]'+' ' + #param;
SELECT #sql
EXEC(#sql)
See a full working example in SQL Server, you should be able to run each block one after the other to see that its selected everything from your table type that you pass in to the stored proc
-- Create the Table type that we will use in the stored proc------------------------
IF NOT EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = 'MyIdTableType')
BEGIN
PRINT 'Creating type [dbo].[MyIdTableType]'
CREATE TYPE [dbo].MyIdTableType AS TABLE (
Id BIGINT
)
END
GO
-- Create a stored proc that uses it ------------------------
CREATE PROCEDURE [dbo].[UsMyTabelType]
#IdsTable AS [dbo].MyIdTableType Readonly
AS
BEGIN
-- Now you have the data you can use it like any normal table and join on it
SELECT * FROM #IdsTable
END
GO
-- Lets test it out ------------------------
DECLARE #myIds AS MyIdTableType
INSERT INTO #myIds (Id)
VALUES
(1),
(2),
(3)
EXEC dbo.UsMyTabelType #IdsTable = #myIds
I've hit a problem with the insert...exec, and I can't find a solution online that will work. I have a stored procedure that retrieves data from an API. It does this by building a command line, running it through xp_cmdshell, and capturing the output to a table (using an insert...exec).
The stored procedure works perfectly, and formats the required data into a nice table
I'm now trying to implement this into my db, but this needs to be called from a number of other stored procedures. They need to be able to see the results of the initial stored procedure, but I've hit a "An INSERT EXEC statement cannot be nested" error, and it won't let me capture the output
I've tried various solutions I've seen suggested online, but so far none of them have worked. The initial stored procedure is calling a command line, so I can't find any other way to call it and capture the output, other than using an insert.....exec, but I still need the formatted output.
I have tried to convert my stored procedure to a function, but I cannot run the xp_cmdshell. I've also looked at getting the initial stored procedure to return the table as an output parameter (even if I create it with a type), but the stored procedure won't allow that
I've also looked at using openset, but I need to be able to pass a parameter to the stored procedure, and I don't think openset will allow this. What could I try next?
EDIT: I've put together a simple example of what I'm trying to do. The stored procedure is retrieving data from a command line. I'm just using an echo command to fudge the data, but in reality, this command line is calling an API, and receiving JSON back. The JSON is then formatted into a SQL table, and output. As this is an API call, I can't see any other way to do it without an insert...exec xp_cmdshell, but this means I cannot capture the output of the stored procedure and use it
create procedure usp_retrieveAPIdata
#inparameter int
as
begin
declare #APIcall varchar(200)
--this would normally be an API call, returning a JSON array
set #APICall='echo f1:"foo" & echo f2:"bar" & echo f1:"Hello" & echo f2:"World"'
declare #resulttable table
(outputfield varchar(100),ID int identity)
insert into #resulttable
exec xp_cmdshell #APICall
declare #formattedtable table
(field1 varchar(100),field2 varchar(100))
declare #rownum int =0
declare #field1 varchar(100)
declare #field2 varchar(100)
declare #currentfield varchar(100)
while exists (select * from #resulttable where ID>#rownum)
begin
set #rownum=#rownum+1
select #currentfield=outputfield from #resulttable where ID=#rownum
if #currentfield like 'f1%'
begin
set #field1=replace(#currentfield,'f1:','')
end
if #currentfield like 'f2%' and #rownum<>1
begin
set #field2=replace(#currentfield,'f2:','')
insert into #formattedtable (field1,field2) values (#field1,#field2)
end
end
select * from #formattedtable
end
go
declare #resulttable table (field1 varchar(100),field2 varchar(100))
insert into #resulttable
exec usp_retrieveAPIdata 1
This is the problem with INSERT EXEC I have run into this many times over the years. Here are a few options - none of them are perfect, each has it's pros/cons but should help get you across the finish line nonetheless.
Sample Procs:
USE tempdb
GO
-- Sample Procs
CREATE PROC dbo.proc1 #a INT, #b INT
AS
SELECT x.a, x.b
FROM (VALUES(#a,#b)) AS x(a,b)
CROSS JOIN (VALUES(1),(2),(3)) AS xx(x);
GO
CREATE PROC dbo.proc2 #a INT, #b INT
AS
DECLARE #x TABLE (a INT, b INT);
INSERT #x(a,b)
EXEC dbo.proc1 5,10;
SELECT x.a, x.b FROM #x AS x;
This will fail due to nesting INSERT EXEC:
DECLARE #a INT = 2, #b INT = 4;
DECLARE #t2 TABLE (a INT, b INT);
INSERT #t2(a,b)
EXEC dbo.proc2 5,10;
Option #1: Extract the stored procedure logic and run it directly
Here I'm simply taking the logic from dbo.proc2 and running it ad-hoc
DECLARE #t2 TABLE (a INT, b INT);
DECLARE #a INT = 2, #b INT = 4;
INSERT #t2 (a,b)
-- Logic Extracted right out of dbo.proc1:
SELECT x.a, x.b
FROM (VALUES(#a,#b)) AS x(a,b)
CROSS JOIN (VALUES(1),(2),(3)) AS xx(x);
SELECT t2.* FROM #t2 AS t2;
Option #2 - Extract the proc logic and run it as Dynamic SQL
DECLARE #t2 TABLE (a INT, b INT);
DECLARE #a INT = 2,
#b INT = 4;
DECLARE #SQL NVARCHAR(4000) = N'
SELECT x.a, x.b
FROM (VALUES(#a,#b)) AS x(a,b)
CROSS JOIN (VALUES(1),(2),(3)) AS xx(x);',
#ParmDefinition NVARCHAR(500) = N'#a INT, #b INT';
INSERT #t2
EXEC sys.sp_executesql #SQL, #ParmDefinition, #a=#a, #b=#b;
SELECT t2.* FROM #t2 AS t2; -- validation
Option #3 - option #2 with the proc code directly from metadata
DECLARE #t2 TABLE (a INT, b INT);
DECLARE #a INT = 2,
#b INT = 4;
DECLARE
#SQL NVARCHAR(4000) =
( SELECT SUBSTRING(f.P, CHARINDEX('SELECT',f.P),LEN(f.P))
FROM (VALUES(OBJECT_DEFINITION(OBJECT_ID('proc1')))) AS f(P)),
#ParmDefinition NVARCHAR(500) = N'#a INT, #b INT';
EXEC sys.sp_executesql #SQL, #ParmDefinition, #a=#a, #b=#b;
The downside here is parsing out what I need. I made my example simple with the logic beginning with a SELECT clause, the real world is not as kind. The upside, compared to manually adding the logic, is that your code will be up-to-date. Changes to the proc automatically change your logic (but can also break the code).
Option #4: Global Temp Table
I haven't really tried this but it should work. You could re-write the proc (proc2 in my example) like this:
ALTER PROC dbo.proc2 #a INT, #b INT, #output BIT = 1
AS
IF OBJECT_ID('tempdb..##x','U') IS NOT NULL DROP TABLE ##x;
CREATE TABLE ##x(a INT, b INT);
INSERT ##x(a,b)
EXEC dbo.proc1 5,10;
IF #output = 1
SELECT x.a, x.b FROM ##x AS x;
GO
I am populating a global temp table with the result set then adding an option to display the output or not. When #output = 0 the result-set will live in ##x, which can be referenced like so:
DECLARE #t2 TABLE (a INT, b INT);
EXEC dbo.proc2 5,10,0;
INSERT #t2(a,b)
SELECT * FROM ##x;
SELECT * FROM #t2;
I think I've cracked it. Weird that you spend all afternoon looking at SQL, then the answer comes to you when you are cleaning out a fish tank
I need to split my sproc into two. The first part calls the API, and receives the answer as a JSON array. JSON is basically text, so rather than convert this into a table, I should just return in as an NVARCHAR(MAX) to the calling sproc.
The calling sproc can then call a second sproc to format this JSON into a table format.
As the first sproc isn't returning a table, SQL won't care about the nested Insert...exec, and as the second sproc isn't using a cmdshell, it doesn't need an insert...exec, so it can receive the results into a table
Here is the above example, but with the sproc split into 2...
begin tran
go
create procedure usp_retrieveAPIdata
#inparameter int,
#resultstring varchar(max) output
as
begin
declare #APIcall varchar(200)
--this would normally be an API call, returning a JSON array
set #APICall='echo f1:"foo" & echo f2:"bar" & echo f1:"Hello" & echo f2:"World"'
declare #resulttable table
(outputfield varchar(100),ID int identity)
insert into #resulttable
exec xp_cmdshell #APICall
set #resultstring=''
select #resultstring=#resultstring + isnull(outputfield,'') + '¶' from #resulttable order by ID --using '¶' as a random row delimiter
end
go
create procedure usp_formatdata (#instring varchar(max))
as
begin
print #instring
declare #resulttable table
(outputfield varchar(100),ID int)
insert into #resulttable (outputfield,ID)
select value,idx+1 from dbo.fn_split(#instring,'¶');
declare #formattedtable table
(field1 varchar(100),field2 varchar(100))
declare #rownum int =0
declare #field1 varchar(100)
declare #field2 varchar(100)
declare #currentfield varchar(100)
while exists (select * from #resulttable where ID>#rownum)
begin
set #rownum=#rownum+1
select #currentfield=outputfield from #resulttable where ID=#rownum
if #currentfield like 'f1%'
begin
set #field1=replace(#currentfield,'f1:','')
end
if #currentfield like 'f2%' and #rownum<>1
begin
set #field2=replace(#currentfield,'f2:','')
insert into #formattedtable (field1,field2) values (#field1,#field2)
end
end
select field1,field2 from #formattedtable
end
go
declare #resulttable table (field1 varchar(100),field2 varchar(100))
declare #outstring varchar(max)
exec usp_retrieveAPIdata 110,#resultstring=#outstring output
insert into #resulttable
exec usp_formatdata #outstring
select * from #resulttable
rollback
Many thanks to everyone who took the time to contribute to this thread
My scenario is like this,
CREATE PROCEDURE SP_1
AS
BEGIN
INSERT INTO #tmpTable(ID, Value)
VALUES(1, 1), (2, 2)
END
GO
CREATE PROCEDURE SP_2
AS
BEGIN
CREATE TABLE #tmpTable(ID INT, Value INT)
EXEC SP_1
SELECT * FROM #tmpTable
DROP TABLE #tmpTable
END
GO
EXEC SP_2
GO
DROP PROCEDURE SP_1
DROP PROCEDURE SP_2
I want to replace this # table with a TABLE VARIABLE (# Table).
I tried to passed table variable as parameter to SP_1 but I should pass table variable as read only parameter. Since it's read only I can't insert into the table variable inside SP_1. Is there any other way I can accomplish this?
It's not gonna work this way. you should encapsulate your query within a string. once done you can change the table names as you like. and then, execute using EXEC sp_executesql.
i've previously answered a similar question which can be found below:
SQL: How to make table name in stored procedure dynamic
here's the example i've provided
declare #sql nvarchar(max)
declare #TableName nvarchar(max)
set #TableName = 'mytable'
set #sql = 'Select * from ' + #TableName
Exec sp_executesql #sql
This worked for me(Sql server 2012), although I did not completely understand what you are trying to achieve:
CREATE PROCEDURE SP_1
AS
BEGIN
Create table #tmpTable (ID INT, Value INT)
INSERT INTO #tmpTable(ID, Value)
VALUES(1, 1), (2, 2)
Select ID,Value from #tmpTable
END
GO
CREATE PROCEDURE SP_2
AS
BEGIN
Declare #tmpTable table (ID INT, Value INT)
Insert into #tmpTable
EXEC SP_1
SELECT * FROM #tmpTable
END
GO
EXEC SP_2
GO
DROP PROCEDURE SP_1
DROP PROCEDURE SP_2
I prefer passing data around as [xml]. It's easier to work with for me. You could build your record set similar to the code below and pass [xml] parameters between your procedures, then parse it out as shown.
declare #record_list [xml] = (select *
from [sys].[objects]
for xml path(N'record'), root(N'record_list'));
select #record_list;
select t.c.value(N'(./schema_id/text())[1]', N'[sysname]') as [schema_id]
, t.c.value(N'(./name/text())[1]', N'[sysname]') as [name]
, t.c.value(N'(./object_id/text())[1]', N'[sysname]') as [object_id]
from #record_list.nodes(N'/record_list/record') as t(c);
I'm writing a stored procedure in SQL Server 2005, at given point I need to execute another stored procedure. This invocation is dynamic, and so i've used sp_executesql command as usual:
DECLARE #DBName varchar(255)
DECLARE #q varchar(max)
DECLARE #tempTable table(myParam1 int, -- other params)
SET #DBName = 'my_db_name'
SET q = 'insert into #tempTable exec ['+#DBName+'].[dbo].[my_procedure]'
EXEC sp_executesql #q, '#tempTable table OUTPUT', #tempTable OUTPUT
SELECT * FROM #tempTable
But I get this error:
Must declare the scalar variable "#tempTable".
As you can see that variable is declared. I've read the documentation and seems that only parameters allowed are text, ntext and image. How can I have what I need?
PS: I've found many tips for 2008 and further version, any for 2005.
Resolved, thanks to all for tips:
DECLARE #DBName varchar(255)
DECLARE #q varchar(max)
CREATE table #tempTable(myParam1 int, -- other params)
SET #DBName = 'my_db_name'
SET #q = 'insert into #tempTable exec ['+#DBName+'].[dbo].[my_procedure]'
EXEC(#q)
SELECT * FROM #tempTable
drop table #tempTable
SQL Server 2005 allows to use INSERT INTO EXEC operation (https://learn.microsoft.com/en-us/sql/t-sql/statements/insert-transact-sql?view=sqlallproducts-allversions).
You might create a table valued variable and insert result of stored procedure into this table:
DECLARE #tempTable table(myParam1 int, myParam2 int);
DECLARE #statement nvarchar(max) = 'SELECT 1,2';
INSERT INTO #tempTable EXEC sp_executesql #statement;
SELECT * FROM #tempTable;
Result:
myParam1 myParam2
----------- -----------
1 2
or you can use any other your own stored procedure:
DECLARE #tempTable table(myParam1 int, myParam2 int);
INSERT INTO #tempTable EXEC [dbo].[my_procedure];
SELECT * FROM #tempTable;
#tempTable's scope is limited to the current procedure.
You could replace the #tempTable with a global temporary table (i.e. ## table), but be very careful with the scope of that table and be sure to drop it when the procedure ends
Was trying to select...into a temp Table #TempTable in sp_Executedsql.
Not its successfully inserted or not but there Messages there written
(359 row(s) affected) that mean successful inserted?
Script below
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = 'select distinct Coloum1,Coloum2 into #TempTable
from SPCTable with(nolock)
where Convert(varchar(10), Date_Tm, 120) Between #Date_From And #Date_To';
SET #Sql = 'DECLARE #Date_From VARCHAR(10);
DECLARE #Date_To VARCHAR(10);
SET #Date_From = '''+CONVERT(VARCHAR(10),DATEADD(d,DATEDIFF(d,0,GETDATE()),0)-1,120)+''';
SET #Date_To = '''+CONVERT(VARCHAR(10),DATEADD(d,DATEDIFF(d,0,GETDATE()),0)-1,120)+''';
'+ #Sql;
EXECUTE sp_executesql #Sql;
After executed,its return me on messages (359 row(s) affected).
Next when trying to select out the data from #TempTable.
Select * From #TempTable;
Its return me:
Msg 208, Level 16, State 0, Line 2
Invalid object name '#TempTable'.
Suspected its working only the 'select' section only. The insert is not working.
how fix it?
Using a global temporary table in this scenario could cause problems as the table would exist between sessions and may result in some problems using the calling code asynchronously.
A local temporary table can be used if it defined before calling sp_executesql e.g.
CREATE TABLE #tempTable(id int);
execute sp_executesql N'INSERT INTO #tempTable SELECT myId FROM myTable';
SELECT * FROM #tempTable;
Local temporary table #table_name is visible in current session only, global temporary ##table_name tables are visible in all sessions. Both lives until their session is closed.
sp_executesql - creates its own session (maybe word "scope" would be better) so that's why it happens.
In your #sql string, don't insert into #TempTable. Instead, call your SELECT statement without an INSERT statement.
Finally, insert the results into your temporary table like so:
INSERT INTO #tmpTbl EXEC sp_executesql #sql
Also, you'll need to declare the temporary table if you use this approach
DECLARE #tmpTbl TABLE (
//define columns here...
)
your temp table in dynamic SQL is out of scope in the non dynamic SQL part.
Look here how to deal with this: A bit about sql server's local temp tables
Temporary tables only live as long as the connection that creates them. I would expect that you're unintentionally issuing the select on a separate connection. You can test this by momentarily doing your insert into a non-temporary table and seeing if your data is there. If that is the case you can go back to your original solution and just be sure to pass the connection object to your select.
declare #sql varchar(1000)
set #sql="select * into #t from table;"
set #sql =#sql + "select * from #t;"
execute SP_EXECUTESQL #sql
This worked for me
declare #sql nvarchar(max)
create table #temp ( listId int, Name nvarchar(200))
set #sql = 'SELECT top 10 ListId, Name FROM [V12-ListSelector].[dbo].[List]'
insert into #temp
exec sp_executesql #sql
select * from #temp
drop table #temp
To work around this issue use a CREATE TABLE #TEMPTABLE command first to generate an empty temp table before running sp_executesql. Then run the INSERT INTO #TEMPTABLE with sp_executesql. This will work. This is how I overcome this problem as I have a setup in which all my queries are usually run via sp_executesql.
This one worked for me:
DECLARE #Query as NVARCHAR(MAX);
SET #Query=(SELECT * FROM MyTable) ;
SET #Query=(SELECT 'SELECT * INTO dbo.TempTable FROM ('+#Query +') MAIN;');
EXEC sp_executesql #Query;
SELECT * INTO #TempTable FROM dbo.TempTable;
DROP TABLE dbo.TempTable;
SELECT * FROM #TempTable;
Note, from T-SQL 2021 onwards, dm_exec_describe_first_result_set() can be used to build a temporary table in the right shape to INSERT INTO - as it gives you the column names and types that will be returned from your dynamic SELECT or EXEC ... so you can build dynamic SQL to ALTER a temporary table into the shape you need.
DECLARE #strSQL NVarChar(max) = 'EXEC [YourSP] #dtAsAt=''2022-11-09'', #intParameter2=42'
--*** Build temporary table: create it with dummy column, add columns dynamically
--*** using an exec of sys.dm_exec_describe_first_result_set() and dropping the dummy column
DROP TABLE IF EXISTS #tblResults;
CREATE TABLE #tblResults ([zz] INT);
DECLARE #strUpdateSQL NVarChar(max);
SELECT #strUpdateSQL = STRING_AGG( CONCAT( 'ALTER TABLE #tblResults ADD ',
QUOTENAME([name]), ' ',
[system_type_name], ';')
, ' ') WITHIN GROUP (ORDER BY [column_ordinal])
FROM sys.dm_exec_describe_first_result_set (#strSQL, NULL, 0)
SET #strUpdateSQL += 'ALTER TABLE #tblResults DROP COLUMN [zz];'
EXEC (#strUpdateSQL);
--*** Now we have #tblResults in the right shape to insert into, and use afterwards
INSERT INTO #tblResults EXEC (#strSQL);
SELECT * FROM #tblResults;
--*** And tidy up
DROP TABLE IF EXISTS #tblResults;