5

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.

Ardalan Shahgholi
  • 11,967
  • 21
  • 108
  • 144
Simon Elms
  • 17,832
  • 21
  • 87
  • 103
  • See: http://stackoverflow.com/questions/28885415/performance-differences-between-different-ways-of-calling-sp-executesql-with-bo – tbone Mar 05 '15 at 21:58
  • Also, afaik you can't use LIKE(), IN() in dynamic SQL using parameters – tbone Mar 05 '15 at 21:58

5 Answers5

8

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

PaulVrugt
  • 1,682
  • 2
  • 17
  • 40
  • In my case, 'Name' is supplied from the front-end application which I think will make the below query SQL injectable. SELECT *ParameterAssignment = ISNULL(@ParameterAssignment + ';','') + 'DECLARE ' + Name + ' ' + Type + ' = (SELECT CAST(Value AS ' + Type + ') FROM *p1 WHERE Name = ''' + Name + ''')' . – rohit Jan 03 '23 at 12:26
3

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.

andowero
  • 439
  • 4
  • 13
  • Not a solution. Still requires knowing the number of parameters up front. You're showing how to call a dynamic SQL string with a known number of fixed parameters. The OP is looking for a solution to call dynamic SQL with an unknown number of parameters. This may require NESTING a dynamic SQL statement, so you can no only generate the sp_executesql and the parameters pass to it dynamically, but also get the data inside the dynamic string that's passed. That may require passing in parameter data through a lone Table Valued Parameter, as in another answer. – Triynko Mar 15 '21 at 18:03
  • @Triynko: Well, you don't need to know the exact number, but the maximum number of parameters. OP can prepare, lets say, 20 `VARCHAR` parameters, 20 `INTEGER` parameters, 20 `DATETIME` and 20 `TABLE` parameters. He would need to declare all of them up front, specify them in `@params` and assign them to the `sp_executesql`. He than can use only one of them and generate SQL with only one of them used. – andowero Mar 17 '21 at 12:46
  • @Triynko: I have edited the answer. Do you still think this doesn't answer the question even a little? – andowero Mar 17 '21 at 12:59
2

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.

richardtallent
  • 34,724
  • 14
  • 83
  • 123
  • I feared this might be the case. The problem is the queries are stored in the database, in a table. But we can pull the appropriate query back into the client-side code, apply the arguments there and execute it. Luckily performance is not an issue. – Simon Elms Aug 30 '13 at 23:31
  • 1
    "which then makes the entire point of parameters moot" Not really. While it may be unusual, there's no fundamental reason why one would NEVER want to do this - for example, I am interested in executing SP's based on definitions in metadata - to accomplish this, I require the functionality as described in the original question. – tbone Aug 19 '14 at 21:33
  • Not entirely true: http://stackoverflow.com/questions/28885415/performance-differences-between-different-ways-of-calling-sp-executesql-with-bo – tbone Mar 05 '15 at 21:59
  • What if the calling code is not C#, but a stored produre in TSQL that's trying to dynamically build the string? That would require the ability to pass an unknown number of local SQL variables to sp_executesql. There needs to be a way to call sp_executesql without knowing the number of parameters ahead of time, which would seem to require passing it some sort of parameter array. – Triynko Mar 15 '21 at 17:58
2

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%';
Simon Elms
  • 17,832
  • 21
  • 87
  • 103
  • This seems viable at first glance, but I think the varying data types of each parameter would make it not workable. – tbone Aug 19 '14 at 21:35
  • @tbone: Yes, you're right. It would only work if you knew, for example, that the parameters were always going to be varchars. So it would be useful only in a very limited case. – Simon Elms Aug 19 '14 at 22:49
  • This scenario is extremely useful when a type of process is assigned what will be its calculations, specifies the calculation (stored procedures) and its parameters. Otherwise it is inflexible, with this post I have solved my case. – Mitolo Sep 28 '17 at 22:25
0

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';
Sunny Patel
  • 7,830
  • 2
  • 31
  • 46
AB Vyas
  • 2,349
  • 6
  • 26
  • 43
  • My question can't have been clear enough. I've edited it to try to explain better. Basically, I want to be able to pass in a query to sp_ExecuteSql which has any number of parameters, and those parameters may have any names. – Simon Elms Aug 30 '13 at 05:08
  • Ok but that parameter you have to define in your query and then pass the parameter value to it – AB Vyas Aug 30 '13 at 05:36
  • That's exactly the problem: Is there any way to pass in a query with an arbitrary number of parameters into sp_ExecuteSql and assign values to those parameters? I've discovered I can pass in the parameter values by position, I don't need to use the parameter names when calling sp_ExecuteSql. But I still need to know how many parameters there will be ahead of time. – Simon Elms Aug 30 '13 at 05:44
  • For dynamically you have to first store in string and the then execute that something like this you have to store query and parameter in string then you have to execute that query – AB Vyas Aug 30 '13 at 06:02
  • 1
    Your dynamic example illustrates the problem: You have to know ahead of time that there are two parameters, called `@ID` and `@FName`. But what if you don't know how many parameters there are, or what they are called? – Simon Elms Aug 30 '13 at 23:37
  • Then you have to pass it in string variable dynamically – AB Vyas Aug 31 '13 at 03:29