dynamic SQL update command - sql

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.

Related

Setting SQL Variable via Dynamic SQL

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'.

Pass the Multiple quotes around #variable wherever single quote is there

Below is my procedure. It is working fine.
Create PROCEDURE [dbo].[spCompanyName]
(
#CompanyName VARCHAR(100))
AS
Begin
DECLARE #sql VARCHAR(MAX)
SET #sql = ' Select EmpID,CompanyName FROM Employee' + CHAR(13) + CHAR(10)
IF len(#CompanyName) > 0
BEGIN
SET #sql = #sql + ' Where (RTRIM(LTRIM(CompanyName)) like ''' + #CompanyName + '%'') ' + char(13) + char(10)
END
PRINT #SQL
EXEC(#sql)
End
exec spCompanyName #CompanyName='So Unique, formerly Sofia''''s'
I need Wherever single quote is there in companyname,if I pass 8 quotes in single quotes I need output.above procedure where do I need to change.
Eg:
exec spCompanyName #CompanyName='So Unique, formerly Sofia''''''''s'
exec spCompanyName #CompanyName='Absolute''''''''s,Strategy''''''''s'
Don't think "I'll throw away all of the useful features of using parameters to separate data from code and start manually trying to protect strings" - keep using parameters.
Rather than
EXEC(#sql)
Have:
EXEC sp_executesql #sql,N'#CompanyName varchar(100)',#CompanyName = #CompanyName
And change:
SET #sql = #sql + ' Where (RTRIM(LTRIM(CompanyName)) like ''' + #CompanyName + '%'') ' + char(13) + char(10)
to:
SET #sql = #sql + ' Where (RTRIM(LTRIM(CompanyName)) like #CompanyName + ''%'') ' + char(13) + char(10)
Or, in the alternative, consider just writing a normal query, no dynamic SQL:
Create PROCEDURE [dbo].[spCompanyName]
(
#CompanyName VARCHAR(100))
WITH RECOMPILE
AS
Begin
Select EmpID,CompanyName FROM Employee
Where RTRIM(LTRIM(CompanyName)) like #CompanyName + '%' or
#CompanyName is null
End
Assuming you're using SQL Server 2008 (with particular patch levels) or later, as specified in Erland Sommarskog's Dynamic Search Conditions in T-SQL
If you are going to create a new stored procedure for this feature then you can have this in much better way then dynamic.
You should execute different select queries based on if CompanyName is passed or not.
You can simply the stored procedure as following.
Create PROCEDURE [dbo].[spCompanyName]
(
#CompanyName VARCHAR(100))
AS
Begin
IF LEN(#CompanyName) > 0
BEGIN
Select EmpID,CompanyName FROM Employee WHERE RTRIM(LTRIM(CompanyName)) like '' + #CompanyName + '%'
END
ELSE
BEGIN
Select EmpID,CompanyName FROM Employee
END
END
I am not sure why you need to pass so many single quotes in the parameter value but to me it looks like you can execute the procedure with following simple way.
EXEC spCompanyName #CompanyName='So Unique, formerly Sofia''s'
EXEC spCompanyName #CompanyName='Absolute''s,Strategy''s'
This should help you finding your solution.

SQL Store a value from an EXECUTE Command into a variable

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

How to dynamically specify a column in stored procedure

I need to write a stored procedure which will update into a particular column, based on parameters. This will be called from a C# app.
The table to be updated is like this:
TableA: Id, Col1, Col2, Col3.
Something like this:
Create Procedure UpdateByCol
#col_num int,
#value string,
#id int
as
Begin
Update TableA Set *How do I specify the column here* = #value where Id = #id
End
Declare #SQL varchar(max)
Set #SQL = 'Update ' + #TableName + ' Set ' + #Col1 + ' = ' + #Col1Val....
Exec (#SQL)
This exposes you to SQL injection though...
There is a way to do this without dynamic sql. Create individual stored procs for each column that is eligible for updating. Then, in your master stored proc, call the applicable stored proc based on the paramter received.
If you really want to do this it can be done with dynamic sql. http://technet.microsoft.com/en-us/library/ms188001.aspx
It is not good practice to do this but if you really wanted to you'd have to use dynamic sql to create the string and execute it like so:
Create Procedure UpdateByCol
#col_num int,
#value string,
#id int
as
Begin
declare #str varchar(2000)
set #str = 'Update TableA Set Col' + convert(varchar,#col) + ' = ''' + #value + ''' where Id = ' + convert(varchar, #id )
exec ( #str )
End
This is a very bad thing to do because it creates a security hole.

How do I pass a column as a parameter in Stored Procedure?

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.