I'm attempting to store a value into a variable from a EXECUTE command. I know I'm suppose to use sp_executesql command, but all examples online are only making more confused. So here is what I'm trying to do.
I have a stored procedure that accepts two parameters (a table name, a room #). To have a dynamic table name, I use dynamic SQL style while using strings. I'm attempting to store a phone number that is either from multiple tables. I got this working so far.
DECLARE #Location varchar(MAX);
DECLARE #Room varchar(10);
DECLARE #Number char(8);
DECLARE #SQLString varchar(MAX);
SET #Location = N'CMPhone.dbo.GardenCottage';
SET #Room = N'202';
SET #SQLString ='SET #Number = (SELECT PhoneNumber FROM ' + #Location + ' WHERE Room = ''' + #Room + ''');';
PRINT(#SQLString);
OUTPUT
SET #Number = (SELECT PhoneNumber FROM CMPhone.dbo.GardenCottage WHERE Room = '202');
SET #Number = (SELECT PhoneNumber FROM CMPhone.dbo.GardenCottage WHERE Room = '202');
PRINT(#Number);
OUTPUT
123-4567
Which is the correct number. Now, here is where the problem comes in. I need to do another query using dynamic SQL so I can use multiple tables again. So in my stored procedure, I need to store my EXEC(#SQLString) into a variable (#Number) so I can use that value, and that's where I'm having problems. I can't get sp_executesql to store the value into #Number. The other query will look something like this
SET #SQLString = ' UPDATE PhoneNumbers SET Active = ''1'' WHERE
PhoneNumber = ''' + #Number + ''';';
EXEC(#SQLString);
If this is confusing in anyway, or you have questions, please ask. Any help is very much appreciated. Thanks
Update #1:
I have this new string now
#SQLString = 'SELECT PhoneNumber FROM ' + #Location ' + ' WHERE Room = ''' + #Room + ''';';
EXECUTE SP_EXECUTESQL #SQLString
gets the correct number, but I don't know how to set up a OUTPUT parameter.
I'm attempting to follow this example from Microsoft
DECLARE #SQLString NVARCHAR(500)
DECLARE #ParmDefinition NVARCHAR(500)
DECLARE #IntVariable INT
DECLARE #Lastlname varchar(30)
SET #SQLString = N'SELECT #LastlnameOUT = max(lname)
FROM pubs.dbo.employee WHERE job_lvl = #level'
SET #ParmDefinition = N'#level tinyint,
#LastlnameOUT varchar(30) OUTPUT'
SET #IntVariable = 35
EXECUTE sp_executesql
#SQLString,
#ParmDefinition,
#level = #IntVariable,
#LastlnameOUT=#Lastlname OUTPUT
SELECT #Lastlname
But I don't see how their declaring the lastlNameOUT variables.
use output variable in your EXECUTE sp_executesql like this:
EXECUTE sp_executesql #SQLString, N'#Number char(8) out',#Number out then you will get #Number value from inside dynamc sql, then you can use that value in other part of the query. hope this helps
Related
I know I am overthinking this, but I've been banging against this for too long so I'm reaching out for help.
This is the statement I'm trying to run: SELECT #cntMax = MAX(id) FROM [Raw_Item-FieldReport]
BUT, the table name is a variable #reportTable
This doesn't work:
SET #sql = 'SELECT #cntMax = MAX(id) FROM #reportTable'
EXEC sp_executesql #sql
I even tried having the actual table name in the SET #sql and that doesn't work either.
I didn't think it would be this difficult, please tell me I'm missing something easy/obvious.
Here's the full bit of code for those who want it:
DECLARE
#inTable nvarchar(255) = 'Raw_Item',
#reportTable nvarchar(255),
#fieldName nvarchar(255),
#cnt int,
#cntMax int,
#sql nvarchar(max)
SET #reportTable = #inTable + '-FieldReport'
SET #cnt = 1
SELECT #cntMax = MAX(id) FROM [Raw_Item-FieldReport]
PRINT #cntMax
SET #cntMax = 0
SET #sql = 'SELECT #cntMax = MAX(id) FROM [Raw_Item-FieldReport]'
EXEC sp_executesql #sql
PRINT #cntMax
SQL Server 12.0.2008.8 (on Azure)
You need to use an output parameter, otherwise SQL Server has no idea how to connect #cntMax in the dynamic SQL to #cntMax not in the dynamic SQL, since they are different scopes. And to protect yourself from SQL injection (some tips here and here), always check that your object exists, and use QUOTENAME() as opposed to manually adding square brackets (and you should always use QUOTENAME() when building object names from user input or variables, even when they don't have bad characters like dashes):
DECLARE #sql nvarchar(max),
#inTable nvarchar(255) = N'Raw_Item',
#reportTable nvarchar(255);
SET #reportTable = N'dbo.' + QUOTENAME(#inTable + '-FieldReport');
IF OBJECT_ID(#reportTable) IS NOT NULL
BEGIN
SET #sql = N'SELECT #cntMax = MAX(id) FROM ' + #reportTable + N';';
EXEC sys.sp_executesql #sql,
N'#cntMax int output',
#cntMax = #cntMax OUTPUT;
PRINT #cntMax;
END
ELSE
BEGIN
PRINT 'Nice try, h#xx0rs!';
END
Always use schema reference (dbo), always use statement terminators, and please try to avoid naming things with invalid identifier characters like dash (-). And one additional tip: always use N prefix on N'nvarchar string literals'.
I am using MSSQL 2016,
I need to be able to update a row on a table dynamically.
I got a stored procedure :
CREATE PROCEDURE sp_lookupData_UpdatelookupValues
(
#FullTableName nvarchar(50),
#Id nvarchar(10),
#Name nvarchar(50),
#Description nvarchar(50)
)
AS
BEGIN
DECLARE #Cmd nvarchar(150) = N'UPDATE ' + #FullTableName + ' SET Name = ' + #Name + ', Description = ' + #Description + ' WHERE ID = ' + #Id + '';
EXECUTE sp_executesql #Cmd;
END
The problem is that Name and Description values are passed into the #Cmd like this :
UPDATE TABLE_NAME SET Name = Private, Description = Default WHERE ID = 1
Instead of 'Private' and 'Default'.
The result is an error where Private is being counted as a column which doesnt exist ( because of the bad format ).
Invalid column name 'Private'.
Put the quotes yourself
Use single quotes around Private and Default.
And since you are using dynamic querying, you have to double the single quotes to escape them.
DECLARE #Cmd nvarchar(150) = N'UPDATE ' + #FullTableName + ' SET Name = ''' + #Name + ''', Description = ''' + #Description + ''' WHERE ID = ' + #Id + '';
Also make sure you try the next solution, since the first one is SQL Injection compatible.
Use sp_executesql parameters
You can also use the parameters inside your #Cmd without doing the concatenation yourself but by passing the parameters to sp_executesql
Also I suggest you to QUOTENAME the #FullTableName parameter in case of spaces inside table's name.
DECLARE #Cmd nvarchar(150) = N'UPDATE QUOTENAME(#FullTableName) SET Name = #Name, Description = #Description WHERE ID = #Id;'
EXEC sp_executesql #Cmd, #FullTableName, #Name, #Description, #Id;
The advantage doing so, is you avoid any parameters not checked by the application to be able to do SQL Injection.
Reference :
https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-executesql-transact-sql
You have to add the quotes yourself. I prefer to use QUOTENAME to keep all the quotes recognizable:
QUOTENAME(#FullTableName, '''')
You cannot use parameters for identifiers. But, you can use parameters for values:
DECLARE #Cmd nvarchar(150) = N'
UPDATE ' + #FullTableName + '
SET Name = #Name,
Description = #Description
WHERE ID = #Id';
EXECUTE sp_executesql #Cmd,
N'#Name nvarchar(50), #Description nvarchar(50), #Id nvarchar(10)',
#Name = #Name, #Description = #Description, #Id = #id;
Unfortunately, the dynamic table name still poses risks, both in terms of SQL injection and syntax errors. I am guessing this is "controlled" code, not code with user input, so the risks might be acceptable. However, you probably should use quotename().
That brings up another issue which is probably the crux of the problem. Why do you have multiple tables with the same columns? Could these -- should these -- all be stored in a single table? This type of code calls into question aspects of the data model.
I'm trying to pass in #accountType, a char value to a stored procedure that uses dynamic SQL. It is declared as char(4) in the procedure. The current error is Incorrect syntax near 'D' if I try to change it I get invalid column: D.
I cannot figure out how dynamic SQL wants me to indicate that the variable is a char. I've tried it many ways, here is the most recent:
set #q = 'Update ' + #statementTable +
' SET Account = '+ #padding + #accountNumber +
' WHERE ClosingDate BETWEEN CAST('''+CONVERT(VARCHAR(20),#proc_dateStart)+''' AS DATE) AND CAST('''+CONVERT(VARCHAR(20),#proc_dateEnd)+''' AS DATE)' +
' AND AccountType =' + ''''+ #accountType +''''
The value is coming from my C# code exactly like this: D
No single quotes or anything around the letter. Any ideas? I'm more than a bit stuck with this.
Something like this, you need to have the parameters actually within the string statement, then when you execute sp_executesql, you then pass what each of those parameters are.
DECLARE #q VARCHAR(MAX)
DECLARE #statementTable VARCHAR(50)
DECLARE #padding VARCHAR(50)
DECLARE #accountNumber CHAR(4)
DECLARE #proc_dateStart VARCHAR(50)
DECLARE #proc_dateEnd VARCHAR(50)
DECLARE #accountType VARCHAR(50)
SET #q = 'Update #statementTable
SET Account = ''#accountNumber''
WHERE ClosingDate BETWEEN CAST(''+CONVERT(VARCHAR(20),#proc_dateStart)+'' AS DATE) AND CAST(''+CONVERT(VARCHAR(20),#proc_dateEnd)+'' AS DATE)
AND AccountType = ''#accountType'''
EXEC sys.sp_executesql #sql, N'#statementTable VARCHAR(50),#accountNumber CHAR(4),#proc_dateStart VARCHAR(50), #proc_dateEnd VARCHAR(50),#accountType VARCHAR(50)',
#statementTable,#accountNumber,#proc_dateStart,#proc_dateEnd,#accountType;
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';
How do I pass and use the column name to retrieve a bigint variable in the actual column?
DECLARE #personID BIGINT,
DECLARE #queryString varchar(500)
Set #queryString = 'Select #personID = ' + #PersonColumnID + ' from dbo.Loss_Witness where WitnessID = #witnessID'
exec(#queryString)
Error message states "Must declare variable '#personID'." I also tried
Set #queryString = 'Select ' + #personID + ' = ' + #witnessPersonID + ' from dbo.Loss_Witness where WitnessID = #witnessID'
And got the error message "Error converting data type varchar to bigint."
Any thoughts?
You need to specify that #personID is an out parameter with the OUTPUT keyword:
DECLARE #SQL NVARCHAR(max)
SET #SQL = 'SELECT #personID = w.' + #PersonColumnID + '
FROM dbo.Loss_Witness w
WHERE w.witnessID = #witnessID '
BEGIN
EXEC sp_executesql #SQL, #personID = #personID OUTPUT, #witnessID
END
There's another example here.
Also note that there is potentially an SQL Injection security hole in your code. If you're not sanitizing #PersonColumnID then you could be in big trouble.