How to Use Dynamic Sql on Update Command? - sql

I Had Update Command. Just Like this.
Update DispatchExceptions
SET TYPE='MWM'
WHERE ID=9801246
I want to fire same command using dynamic sql.
DECLARE #ColumnName varchar(20)=N'TYPE'
DECLARE #ColumnValue char(3)='MWM'
DECLARE #ID INTEGER = 9801246
declare #Query nvarchar(max)
SET #Query = 'Update DispatchExceptions SET '+ QUOTENAME(#ColumnName) + '''=' + #ColumnValue + '''WHERE ID =' + #ID +''
EXEC (#Query)
But it show following error.
Conversion failed when converting the nvarchar value 'Update DispatchExceptions SET [TYPE]'=MWM'WHERE ID =' to data type int.
How can I use dynamic sql in the situation. Any suggestion.

The problem here is specifically the part ' + #ID. #ID is the datatype int, which has a higher datatype precedence than nvarchar; thus any other expressions will be implicitly cast to an int (and as I'm sure you're aware, 'Update DispatchExceptions SET...' isn't an int).
If you parametrise your query, you won't have this problem:
DECLARE #ColumnName sysname = N'TYPE'; --Changed the datatype here to sysname, as the caters for all possible object names
--sysname is actually a synonym for nvarchar(128), and 128 is the maximum length for an object's name
DECLARE #ColumnValue char(3) = 'MWM';
DECLARE #ID integer = 9801246;
DECLARE #Query nvarchar(MAX);
SET #Query = N'Update DispatchExceptions SET ' + QUOTENAME(#ColumnName) + N' = #value WHERE ID = #ID;';
EXEC sp_executesql #Query,
N'#Value char(3), #ID int',
#Value = #ColumnValue,
#ID = #ID;
Also, to the OP, well done on the use of QUOTENAME. This is far too often missed out, and thus leaves your (dynamic) SQL open to injection.

The DBMS fails to add
9801246
to
Update DispatchExceptions SET [TYPE]'=MWM'WHERE ID =
because the latter is a string and canot be converted to number, which would be necessary in order to add the number 9801246 to it :-)
One solution:
DECLARE #ID varchar(7) = '9801246'
Another solution:
SET #Query = 'Update DispatchExceptions SET '+ QUOTENAME(#ColumnName) +
'''=' + #ColumnValue + '''WHERE ID =' + CAST(#ID AS VARCHAR(7)) +''

Simply cast your #ID as varchar and correct quotation before #ColumnValue as given below:
SET #Query = 'Update DispatchExceptions SET '+QUOTENAME(#ColumnName)+'='''+#ColumnValue+''' WHERE ID ='+cast(#ID as varchar)+'';

Related

Concatenation problem in Dynamic sql query

I'm trying to concatenate the string with multiple variables and go for exec. Unfortunately I'm facing the conversion problem as:
Conversion failed when converting the varchar value 'Select #ExistingIds= CBSE_IX_J from ##tempPivot where EmployeeID=' to data type int.
My query is :
SET #ExecColumn = concat('Select #ExistingIds= '+#TSectionName +' from ##tempPivot where EmployeeID='+CAST(#TUserID as INT),'')
PRINT #ExecColumn
EXEC (#ExecColumn)
The "simple" answer is don't concatenate raw string values into your dynamic statement, and parametrise your code. This is a bit of guesswork, however, is far safer than the SQL Injection hole you have right now:
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'SELECT #ExistingIDs = ' + QUOTENAME(#TSectionName) + NCHAR(13) + NCHAR(10)+
N'FROM ##tempPivot' + NCHAR(13) + NCHAR(10) +
N'WHERE EmployeeID = #TUserID;';
PRINT #SQL;
EXEC sp_executesql #SQL,
N'#TUserID int, #ExistingIds int OUTPUT', --guessed datatypes and that #ExistingIds is an OUTPUT
#TUserID = #TUserID,
#ExistingIds = ExistingIds OUTPUT;
Note: the fact that your variable is called #ExistingIDs implies you want to store multiple values in that variable. #ExistingIDs is a scalar value, it will only hold a scalar (single) value. If the query above returns multiple rows, only the value of from the last row will be returned. For example:
DECLARE #i int;
SELECT #i = I
FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9))V(I)
ORDER BY I;
SELECT #i;
Notice that #i has the value 9, not '1,2,3,...,9'.
You would seem to want:
DECLARE #SQL nvarchar(MAX);
DECALRE #ExistingIds NVARCHAR(MAX);
SET #SQL = N'
SELECT #ExistingIDs = STRING_AGG(#TSectionName, '''')
FROM ##tempPivot
WHERE EmployeeID = #TUserID
';
-- Cannot have identifiers as parameters, so use `REPLACE()`
SET #SQL = REPLACE(#SQL, '#TSectionName', QUOTENAME(#TSectionName);
EXEC sp_executesql #SQL,
N'#TUserID int, #ExistingIds NVARCHAR(MAX) OUTPUT', --guessed datatypes and that #ExistingIds is an OUTPUT
#TUserID=#TUserID,
#ExistingIds=#ExistingIds OUTPUT;
In older versions of SQL Server, you need another approach for concatenating strings. For instance
SET #SQL = N'
SELECT #ExistingIDs = (SELECT #TSectionName
FROM ##tempPivot
WHERE EmployeeID = #TUserID
FOR XML PATH ('')
)
';

Prevent dynamic SQL search from SQL injection

How can I make the following code SQL injection safe? I know that the problem is the following line:
SET #sqlCommand = #sqlCommand + 'Event.Name LIKE ' + '''%' + #name + '%'''
But I don't know how to make it SQL injection safe. I heard something about REPLACE but this doesn't solve the problem as a whole.
CREATE PROCEDURE searchEvents #name VARCHAR(50), #location VARCHAR(20), #postcode CHAR(4), #address VARCHAR(40), #startDate DATETIME, #endDate DATETIME
AS
DECLARE
#sqlCommand NVARCHAR(MAX) = 'SELECT Event.Name, Description, Location.Name AS Location, Postcode, Address, StartDate, EndDate, Website FROM Event JOIN Location ON Event.LocationID = Location.LocationID',
#parameters NVARCHAR(MAX),
#whereIncluded BIT = 0
BEGIN
IF #name IS NOT NULL
BEGIN
IF #whereIncluded = 0
BEGIN
SET #sqlCommand = #sqlCommand + ' WHERE '
SET #whereIncluded = 1
END
ELSE
SET #sqlCommand = #sqlCommand + ' AND '
SET #sqlCommand = #sqlCommand + 'Event.Name LIKE ' + '''%' + #name + '%'''
END
-- It's the same if clause for all parameters like above
SET #parameters = '#p_name VARCHAR(50), #p_location VARCHAR(20), #p_postcode CHAR(4), #p_address VARCHAR(40), #p_startDate DATETIME, #p_endDate DATETIME'
EXEC sp_executesql
#sqlCommand,
#parameters,
#p_name = #name,
#p_location = #location,
#p_postcode = #postcode,
#p_address = #address,
#p_startDate = #startDate,
#p_endDate = #endDate
END
Parametrise your SQL. Statements like SET #sqlCommand = #sqlCommand + 'Event.Name LIKE ' + '''%' + #name + '%''' are awful.
I'm not going to go too indepth here, there are 100's of example on how to make your SQL "safe(r)". HOwever, her's a few pointers...
Parametrisation:
Concatenating strings for variables is a sure way to leave yourself open to injection. Take the simple example below:
DECLARE #SQL nvarchar(MAX);
DECLARE #name varchar(1000);
SET #SQL = N'
SELECT *
FROM MyTable
WHERE [Name] = ''' + #Name + ''';';
EXEC (#SQL);
This is an awful example of Dynamic SQL. If you set the value of #name to ''; DROP TABLE MyTable;--' then SQL statement becomes:
SELECT *
FROM MyTable
WHERE [Name] = ''; DROP TABLE MyTable;-- ';
Oh good! Your table, MyTable has been dropped. The correct way would be:
DECLARE #SQL nvarchar(MAX);
DECLARE #name varchar(1000);
SET #SQL = N'
SELECT *
FROM MyTable
WHERE [Name] = #dName;';
EXEC sp_executesql #SQL, N'#dname varchar(1000)', #dName = #Name;
Dynamic Objects:
This is another common mistake people make. They have a query along the lines of:
DECLARE #SQL nvarchar(MAX);
DECLARE #Table varchar(1000);
SET #SQL = N'SELECT * FROM ' + #Table;
EXEC (#SQL);
This suffers exactly the same problem as above. You can't pass a variable as an object name, so you need to do this a little differently. This is by preferred method:
DECLARE #SQL nvarchar(MAX);
DECLARE #Table sysname; --notice the type here as well.
SELECT #SQL = N'SELECT *' + NCHAR(10) +
N'FROM ' + (SELECT QUOTENAME(t.[name]) --QUOTENAME is very important
FROM sys.tables t
WHERE t.[name] = #Table) + N';';
PRINT #SQL;
EXEC sp_executesql #SQL;
Why am I querying sys.tables? Well, this means if you do pass a nonsense table name in, the value of #SQL will be NULL; meaning that the dynamic SQL is completely harmless.
Like I said, this is just the basics; SO isn't the right place for a full answer here. There are 100's of articles on this subject, and you'll learn far more via your own research.
You can just...
SELECT
...
WHERE
#name IS NULL
OR Event.Name LIKE '%' + #name + '%'
You don't even need dynamic SQL in this case, so you can execute the above SQL directly, instead of through sp_executesql.
Be careful about the performance though - a LIKE operand starting with % may cause a full table (or clustered index) scan.

Pass date parameter to dynamic sql

I'm having a very hard time framing this query. The error is - Conversion failed when converting date from character string.
It is a generic query to work for all tables. All was working good, but i had to add a date parameter.
SQL:
DECLARE #qryUpd nvarchar(MAX), #params nvarchar(MAX), #TableName nvarchar(50),
#ColName nvarchar(50),#Id nvarchar(50),#ModifiedBy nvarchar(50),
#ModifiedDate datetime
set #qry = 'update '+#TableName+' set Deleted=1, ModifiedBy='+''''+#Id+''',
ModifiedDate='+''''+#ModifiedDate+''''+' where '+#Colname+'='+''''+
#Id+''''
execute sp_executesql #qry
C#:
public void test(int id, int userid)
{
sqlCommand cmd = new sqlcommand("TestFunc",con);
cmd.commandtype = commandtype.storedprocedure;
cmd.parameters.addwithvalue("#TableName","tblArea");
cmd.parameters.addwithvalue("#ColName","AreaId");
cmd.parameters.addwithvalue("#Id",id);
cmd.parameters.addwithvalue("#ModifiedBy",userid);
cmd.parameters.addwithvalue("#ModifiedDate",system.datetime.now.tostring());
}
With the exception of the dynamic table name and the column, you can at least partially parameterize your query, which will take away the headache of escaping quotes, and also give some protection against Sql Injection (since the table and column are still vulnerable and need to be validated):
DECLARE #qry AS NVARCHAR(MAX);
set #qry = N'update '+#TableName +
' set Deleted=1, ModifiedBy=#ModifiedBy, ModifiedDate=#ModifiedDate
where ' + #Colname + ' = #Id';
exec sp_executesql #qry,
N'#Id nvarchar(50), #ModifiedBy nvarchar(50), #ModifiedDate datetime',
#Id = #Id,
#ModifiedBy = #ModifiedBy,
#ModifiedDate = #ModifiedDate;
SqlFiddle here
You can then bind your .Net parameters for #Id, #ModifiedBy and #ModifiedDate directly with the native types
You are passing the parameters in the wrong way. Use this:
DECLARE #ParmDefinition nvarchar(500);
set #ParmDefinition = '#Id int, #ModifiedDate datetime'
set #qry = 'update '+#TableName+' set Deleted=1, ModifiedBy=#Id, ModifiedDate=#ModifiedDate where ' + #Colname + '=#Id'
execute sp_executesql #qry, #ParmDefinition, #ModifiedDate, #Id
Also pass parameter as DateTime instead of string by removing the ToString
The column ModifiedDate is a datetime field then you must pass the value as a a datetime not as a string
cmd.parameters.addwithvalue("#ModifiedDate",DateTime.Now);
Try to change your last C# row:
cmd.parameters.addwithvalue("#ModifiedDate",system.datetime.now.tostring());
with this:
cmd.Parameters.AddWithValue("#ModifiedDate",System.Datetime.Now.ToString("yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture);

Select a column using a variable?

I was wondering if there is a way to select a column by using a SQL variable. Eg. Table is -
ID, Name, Address
DECLARE #Column varchar(25)
SET #Column = 'Name' -- This can be another column also
SELECT #Column
FROM MyTable
This shows me 'Name' as many times as there are rows in my table.
Is it even possible to do what I want ?
thanks.
Can do this with dynamic SQL:
DECLARE #Column varchar(25)
,#sql VARCHAR(MAX)
SET #Column = 'Name' -- This can be another column also
SET #sql = 'SELECT '+#Column+'
FROM MyTable
'
EXEC (#sql)
You can test your dynamic sql queries by changing EXEC to PRINT to make sure each of the resulting queries is what you'd expect.
You can use dynamic SQL for that:
DECLARE #Column nvarchar(25)
SET #Column = 'Name' -- This can be another column also
DECLARE #sql nvarchar(max) = N'SELECT ' + #Column + N' FROM MyTable'
exec(#sql)
Sql is currently interpreting your variable as a string.
From a previous answer on stack overflow:
DECLARE #Column varchar(25)
SET #Column = 'Name' -- This can be another column also
SET #sqlText = N'SELECT ' + #Column + ' FROM MyTable'
EXEC (#sqlText)

SQL Linked Server query with Parameters

I need to select value from SQL Linked Server & get it to loacal variable
This is what I've written so far:
DECLARE #SQLQUERY AS VARCHAR(1000)
DECLARE #FINALQUERY AS VARCHAR(1000)
DECLARE #OutVal AS VARCHAR(10)
SET #SQLQUERY = 'SELECT Field1 FROM Table1 WHERE Field2=' + CAST(#var1 AS VARCHAR)
SET #FINALQUERY = 'SELECT #OutVal=Field1 FROM OPENQUERY(LINKEDSERVER,' + '''' + #SQLQUERY + '''' + ')'
EXEC(#finalQuery)
but this is wrong as it does not set the local variable(#OutVal).
Instead of exec, use sp_execute_sql with an output parameter:
exec sp_executesql #FinalQuery, N'#OutVal output', #OutVal = #OutVal out
Since sp_executesql expects nvarchar parameters, be sure to change the definition of #FinalQuery to nvarchar(max).
#OutVal in query string does not recognized as a variable. use a function or return table statement.