Execute Stored Procedure from Stored Procedure w/ dynamic SQL capturing output - sql

Inside a stored procedure (A) I need to call a stored procedure (X) inside a specific database and capture the output. X returns a single value.
From what I understand I need to provide the DB name of X to the stored procedure in A and I need to use dynamic SQL to build the query on execution targeting the desired database.
What am unable to figure out is how to capture output from X in A to work with the result.

You could use sp_executesql to dynamically call your nested Stored Procedure.
DECLARE #db AS SYSNAME
DECLARE #return_value AS INT
DECLARE #output_value AS INT
DECLARE #sql AS NVARCHAR(MAX)
-- Set your DB name
SET #db = N'mydb'
/*
Use sp_executesql to dynamically pass in the db and stored procedure
to execute while also defining the values and assigning to local variables.
*/
SET #sql = N'EXEC #rtn = ' + #db + '.dbo.[your_stored_procedure] #output OUTPUT'
EXEC sp_executesql #sql
, N'#rtn AS INT, #output AS INT OUTPUT'
, #return_value = #rtn
, #output_value = #output OUTPUT

Adding to the above answer, following is the way to call stored procedures dynamically by passing parameters.
DECLARE #SpName VARCHAR(1000)
SELECT #SpName = DeleteLiveSP FROM dbo.ArchivalInfo (NOLOCK) WHERE TableName = #TableName
DECLARE #SqlString nvarchar(2000)
DECLARE #ParamDef nvarchar(2000)
SET #SqlString = N'exec '+#SpName + ' #CriteriaParam'
SET #ParamDef = N'#CriteriaParam XML'
EXECUTE sp_executesql #SqlString ,#ParamDef, #CriteriaParam = #Criteria

Related

Cannot get output variable from stored procedure when procedure written in dynamic sql

I am writing a procedure to produce an int output variable, but I'm not sure how to do this using dynamic sql. If I execute the below procedure I get the #AnlyNum value displayed in the results screen, but I just want #AnlyNum variable set with a value so I can use it. Thank you.
Create procedure [dbo].[sp_test] #Db varchar(50), #RwNum int, #AnlyNum int output
As
Begin
Declare #Sql nvarchar(max) =
'Select ''#AnlyNum'' = (Select AnlyId From '+#Db+'..Test order by AnlyId desc OFFSET '+convert(varchar(10),#RwNum)+' rows fetch next 1 rows only)'
End
exec(#Sql)
This removes SQL injection concerns by properly escaping the database name and also dynamically executing against that database instead of embedding the database name in the command. Also, you don't need #RwNum to be dynamic.
CREATE PROCEDURE dbo.test
#Db sysname,
#RwNum int,
#AnlyNum int output
AS
BEGIN
SET NOCOUNT ON;
DECLARE #exec nvarchar(max) = QUOTENAME(#Db) + N'.sys.sp_executesql',
#sql nvarchar(max) = N'SELECT #AnlyNum = AnlyId
From dbo.Test order by AnlyId desc
OFFSET #RwNum rows fetch next 1 rows only);';
EXEC #exec #sql, N'#AnlyNum int output, #RwNum int',
#AnlyNum output, #RwNum;
END

Query that saves data in a variable

I am writing an SP in SQL SERVER, and I need one of the steps to execute a query that comes with data from another variable and save that result in another variable for later use in SP. The code I am using displays the following error:
Procedure expects parameter '#handle' of type 'int'.
DECLARE #retorno INT, #sql NVARCHAR(4000), #parametro NVARCHAR(4000), #caminho NVARCHAR(4000)
SET #caminho = '\\arquivos\CÉLULA DE INOVAÇÃO\SQL\19072019.CSV'
SET #parametro = N'#retornoOut INT OUTPUT'
SET #sql = 'SELECT TOP 1 SUBSTRING(RIGHT('''+#caminho+''',12),1,8)'
PRINT #SQL
EXEC sp_execute #sql, #parametro, #retornoOut=#retorno OUTPUT;
The purpose of the code is from a file passed by the user, I can get the name from the given path. I don't know what could be wrong with id.
The problem is that youre using sp_execute , when you should use sp_executesql
this works fine
DECLARE #retorno INT, #sql NVARCHAR(4000), #parametro NVARCHAR(4000), #caminho NVARCHAR(4000)
SET #caminho = '\\arquivos\CÉLULA DE INOVAÇÃO\SQL\19072019.CSV'
SET #parametro = N'#retornoOut INT OUTPUT'
SET #sql = 'SELECT TOP 1 SUBSTRING(RIGHT('''+#caminho+''',12),1,8)'
PRINT #SQL
EXEC sp_executesql #sql, #parametro, #retornoOut=#retorno OUTPUT;
You don't need to use dynamic SQL for this. You can just use set or a simple select:
Using set:
SET #retorno = SUBSTRING(RIGHT(#caminho,12),1,8)
Using select:
SELECT #retorno = SUBSTRING(RIGHT(#caminho,12),1,8)

Is it possible to supply sp_ExecuteSql parameter names dynamically?

Is it possible to supply the list of parameters to sp_ExecuteSql dynamically?
In sp_ExecuteSql the query and the parameter definitions are strings. We can use string variables for these and pass in any query and parameter definitions we want to execute. However, when assigning values to the parameters, we cannot seem to use strings or string variables for the parameter names.
For example:
DECLARE #SelectedUserName NVARCHAR(255) ,
#SelectedJobTitle NVARCHAR(255);
SET #SelectedUserName = N'TEST%';
SET #SelectedJobTitle = N'%Developer%';
DECLARE #sql NVARCHAR(MAX) ,
#paramdefs NVARCHAR(1000);
SET #sql = N'select * from Users where Name LIKE #UserName '
+ N'and JobTitle LIKE #JobTitle;'
SET #paramdefs = N'#UserName nvarchar(255), #JobTitle nvarchar(255)';
EXEC sp_ExecuteSql #sql, #paramdefs, #UserName = #SelectedUserName,
#JobTitle = #SelectedJobTitle;
The query #sql, and the parameter definitions, #paramdefs, can be passed into sp_ExecuteSql dynamically, as string variables. However, it seems to me that when assigning values to the parameters we cannot assign dynamically and must always know the number of parameters and their names ahead of time. Note in my example how I could declare parameters #UserName and #JobTitle dynamically and pass in that declaration as a string variable, but I had to explicitly specify the parameter names when I wanted to set them. Is there any way around this limitation?
I would like to be able to both declare the parameters dynamically and assign to them dynamically as well. Something like:
EXEC sp_ExecuteSql #sql, #paramdefs,
N'#UserName = #SelectedUserName, #JobTitle = #SelectedJobTitle';
Note that this doesn't actually work but illustrates the sort of thing I'd like to happen. If this sort of thing worked then I could pass in different queries with different numbers of parameters which have different names. The whole thing would be dynamic and I wouldn't have to know the names or numbers of parameters beforehand.
You can do this by using a table valued parameter as the only parameter:
DECLARE #YourQuery NVARCHAR(MAX0 = '<your dynamic query>'
CREATE TYPE dbo.SqlVariantTable AS TABLE
(
[Name] VARCHAR(255),
Type VARCHAR(255),
Value SQL_VARIANT
)
DECLARE #Table SqlVariantTable;
-- Insert your dynamic parameters here:
INSERT INTO #Table
VALUES
('Parameter1', 'VARCHAR(255)', 'some value'),
('Parameter2', 'INT', 3),
DECLARE #ParameterAssignment NVARCHAR(MAX)
SELECT #ParameterAssignment = ISNULL(#ParameterAssignment + ';','') + 'DECLARE ' + Name + ' ' + Type + ' = (SELECT CAST(Value AS ' + Type + ') FROM #p1 WHERE Name = ''' + Name + ''')'
FROM #Table
SET #YourQuery = #ParameterAssignment + ';' + #YourQuery
EXEC SP_EXECUTESQL #YourQuery, N'#p1 SqlVariantTable READONLY', #Table
Now you can simpy insert the parameters into the #Table variable, and they will be present with they original name and type within the query exeuted in the SP_EXECUTESQL. Only make sure you do not use VARCHAR(MAX) or NVARCHAR(MAX) variable types, since they are not supported by SQL_VARIANT. Use (for instance) VARCHAR(4000) instead
I also thought about this and couldn't find anything better than this:
BEGIN
DECLARE
#p1 int, #p2 int, #p3 int, #p4 int...;
DECLARE
#DynamicSQL NVARCHAR(MAX);
SET
#p1 = {some logic},
#p2 = {some different logic},
#p3 = {another logic},
#p4 = {yet another logic},
...;
SET
#DynamicSQL =
N'
some statement
doing
somethin
WHERE
someColumn = #p1
AND someAnotherColumn = #p2
/*no more parameters used below this line*/
';
exec sp_executesql
#stmt = #DynamicSQL,
#params = '#p1 int, #p2 int, #p3 int, #p4 int...'
#p1 = #p1, #p2 = #p2, #p3 = #p3, #p4 = #p4, ...
END;
Notice, that #DynamicSQL uses only 2 out of the 4 possible parameters. Parameters #p1 int, #p2 int, #p3 int, #p4 int... represent the maximum number of parameters you can use in your #DynamicSQL.
You have to have a predefined maximum number of parameters that can be used, and you build the #DynamicSQL statement only with some subset of them. Parameters defined in #params that are not present in the #stmt statement are ignored.
It is not 100 % universal, but I guess that using more than 200 dynamic parameters smells of code smell.
You're trying to work one level too high in abstraction.
Arbitrary parameters requires dynamic SQL, a.k.a. building SQL via strings, which then makes the entire point of parameters moot.
Instead, this should be handled as parameters in the calling code, such as C#, which will allow you to take any SQL statement in a string, apply an arbitrary number of arguments, and execute it.
While this doesn't answer my question I thought it may be useful for others in similar situations. I've discovered the following:
If you have a fixed number of parameters but don't know their names you can pass the parameter values by position rather than name. The following will work:
exec sp_ExecuteSql
#sql,
#paramdefs,
#SelectedUserName, #SelectedJobTitle;
or
exec sp_ExecuteSql
#sql,
#paramdefs,
N'TEST%', N'%Developer%';
Please try this.
Declare #UName varchar(50)
Declare #Job varchar(50)
Set #UName = 'TEST%'
Set #Job = '%Developer%'
exec sp_ExecuteSql #sql, #paramdefs, #UserName = #UName, #JobTitle = #Job;
May this will help you.
Ref From technet.Microsoft.com
Ex.
DECLARE #IntVariable int;
DECLARE #SQLString nvarchar(500);
DECLARE #ParmDefinition nvarchar(500);
/* Build the SQL string one time.*/
SET #SQLString = N'SELECT BusinessEntityID, NationalIDNumber, JobTitle, LoginID
FROM AdventureWorks2012.HumanResources.Employee
WHERE BusinessEntityID = #BusinessEntityID';
SET #ParmDefinition = N'#BusinessEntityID tinyint';
/* Execute the string with the first parameter value. */
SET #IntVariable = 197;
EXECUTE sp_executesql #SQLString, #ParmDefinition,
#BusinessEntityID = #IntVariable;
/* Execute the same string with the second parameter value. */
SET #IntVariable = 109;
EXECUTE sp_executesql #SQLString, #ParmDefinition,
#BusinessEntityID = #IntVariable;
For dynamic you have to pass something like this
EXECUTE sp_executesql N'Select * from Admin WHERE ID = #ID and FirstName=#FName',
N'#ID tinyint, #FName varchar(250)',
#ID = 2, #FName = 'admin';

Procedure to insert Xml Into Sql Server -- Must Declare Scalar Variable

I am using the below procedure to try and insert xml via the filepath into a xml column. I am getting an error must declare scalar variable for ForeignId. Is there a better way of doing what I am trying to do, or am I on the right path?
Here is the procedure
ALTER PROC [dbo].[InsertXml] #path nvarchar(100)
,#ForeignId uniqueidentifier
AS
BEGIN
SET NOCOUNT ON
DECLARE #SQL nvarchar(4000) =
'INSERT INTO XmlTable(XmlId
, ForeignId
, TestXml)
SELECT NEWID()
, #ForeignId
,* FROM OPENROWSET(
BULK ''' + #path + ''',
SINGLE_BLOB) AS x;'
EXECUTE(#SQL);
RETURN ##ERROR;
END
When you're executing the SQL statement using EXECUTE(SQL) it has no access to the #ForeignId value
One way to solve this is to use sp_excuteSQL and do this instead of EXECUTE(#SQL);
DECLARE #ParmDefinition nvarchar(500);
SET #ParmDefinition = N'#ForeignId uniqueidentifier';
EXECUTE sp_executesql #SQL, #ParmDefinition, #ForeignId ;
You could also just concatenate the #ForeignId to your sql string but I can't recall if there are issues with that when using a uniqueidentifier

Using variable inside dynamic SQL

I have the below SQL..What I am trying to do is use the Parameter defined at the stored procedure level inside dynamic SQL:
CREATE PROCEDURE [dbo].[Test]
(#DealID NVARCHAR(500),
#OUTPUT NVARCHAR(MAX) OUTPUT,
#FeeType CHAR(1)
) -- I want to use this parameter inside dynamic SQL query
AS
DECLARE #exec_str NVARCHAR(MAX)
DECLARE #ParmDefinition NVARCHAR(MAX)
BEGIN
SET #exec_str = N'DECLARE #ParmDefinition NVARCHAR(500)
SELECT * FROM #FeeType' --This is where I want to use the variable
DECLARE #ParamDefinition nvarchar(max)
SET #ParamDefinition = N'#OUTPUT NVARCHAR(MAX) OUTPUT'
EXEC sp_executesql #exec_str, #ParamDefinition
Can someone please tell me how to do it?
Thanks
In SQL Server Identifiers can't be parameterized.
Since you are using dynamic SQL anyway, you can do something like this:
SET #exec_str= N'Select * from '+ #FeeType
EXEC(#exec_str)
However, this is vulnerable to SQL injection attacks. To reduce the risk to minimum you should check first that such a table name exists, and I would also use quotename just to be on the safe side:
IF EXISTS
(
SELECT 1
FROM Information_Schema.Tables
WHERE TABLE_NAME = #FeeType
)
BEGIN
SET #exec_str= N'Select * from '+ QUOTENAME(#FeeType)
EXEC(#exec_str)
END