T-SQL Procedure, scalar variable error even after successful updation - sql

--sp_executesql version
--SET #SQLQUERY = 'UPDATE #TableName SET Brief = #Brief,
-- [Full] = #Full,
-- CreatedBy = #CreatedBy,
-- Department = #Department,
-- Answer = #Answer WHERE Id=#Id';
--SET #ParamDefinition=N'#TableName nvarchar(50),#Brief nvarchar(50),#Full nvarchar(MAX),#CreatedBy varchar(256),#Department varchar(256),#Answer nvarchar(MAX),#Id int'
-- exec sp_executesql #SQLQUERY,#ParamDefinition,#TableName,#Brief,#Full,#CreatedBy,#Department,#Answer,#Id;
-- exec version
SET #SQLQUERY = 'UPDATE ' + #TableName + ' SET
Brief ='+ #Brief+',
[Full] ='+ #Full+',
CreatedBy ='+ #CreatedBy+',
Department ='+ #Department+',
Answer ='+#Answer+' WHERE Id='+CAST(#Id as nvarchar(10))
print #SQLQUERY;
EXEC (#SQLQUERY)
I have used both EXEC and sp_executesql procedures to execute my dynamic query but both are failing.
In case of EXEC the dynamic query is not set to the #SQLQUERY variable (seen after debugging), in case of sp_executesql I get scalar variable error though database is updated and I have already passed everything to it.

Case is very simple. You cannot parametrize table/column name in UPDATE statement:
SET #SQLQUERY = 'UPDATE #TableName --here is problem
SET Brief = #Brief,
[Full] = #Full,
CreatedBy = #CreatedBy,
Department = #Department,
Answer = #Answer
WHERE Id=#Id';
SET #ParamDefinition=N'#TableName nvarchar(50),#Brief nvarchar(50),
#Full nvarchar(MAX), #CreatedBy varchar(256),
#Department varchar(256),#Answer nvarchar(MAX),#Id int'
EXEC dbo.sp_executesql #SQLQUERY,#ParamDefinition,
#TableName,#Brief,#Full,
#CreatedBy,#Department,#Answer,#Id;
Use substitution instead:
SET #SQLQUERY = N'UPDATE <tab_name>
SET Brief = #Brief,
[Full] = #Full,
CreatedBy = #CreatedBy,
Department = #Department,
Answer = #Answer
WHERE Id = #Id';
SET #SQLQUERY = REPLACE(#SQLQUERY, '<tab_name>', QUOTENAME(#TableName));
SET #ParamDefinition=N'#Brief nvarchar(50),#Full nvarchar(MAX),
#CreatedBy varchar(256),#Department varchar(256),
#Answer nvarchar(MAX),#Id int';
EXEC [dbo].[sp_executesql] #SQLQUERY,
#ParamDefinition,
#Brief,#Full,#CreatedBy, #Department,#Answer,#Id;
Notes:
Table name should have SYSNAME datatype.
It is a good practice to quote identifiers with QUOTENAME (to avoid potential SQL Injection attacks).
I guess #CreatedBy is datetime that is why I do not understand why it is passed as varchar(256).
It is a good practice to end every statement with ;. In future versions this will be mandatory.

Related

SQL Pass a string to a Stored Procedure that is not a Column Name

Can I pass a String to a Stored Procedure that is not a Column Name?
I need to call the StoredProcedure from C#.
The following does not work as the parameter can't be defined without it's Type, but shows what I am trying to do. Problem is that Sql is looking at #stringToIdentifyDataTable as a ColumnName, which seems fair, but not what I am trying to do.
Alter PROCEDURE [dbo].[PutNewTypeSource] #stringToIdentifyDataTable,
#ID int, #Description varchar(50), #Active bit
AS
DECLARE
#Query AS VARCHAR(MAX),
#Field_Out AS VARCHAR(MAX)
SELECT #Field_Out = CASE #stringToIdentifyDataTable
WHEN 'ReferralSource' THEN '[Adm].[JobReferralSource]'
WHEN 'ReferralSource2' THEN '[Adm].[JobReferralSource2]'
END
SET #Query = concat('
IF EXISTS (SELECT ID FROM ',#Field_Out,' WHERE Description= ',#Description,')
BEGIN
UPDATE ',#Field_Out,
'SET Active = ',#Active,
'WHERE Description= ',#Description,';
END')
EXEC (#Query)
exec [PutNewTypeSource] 'ReferralSource', 1, 'Description1', 0
If I understand correctly what you could do is this. note that I properly quote your object, and importantly parametrise you parameters. What you have before was wide open to injection:
ALTER PROCEDURE [dbo].[PutNewTypeSource] #Referral varchar(50), #Description varchar(50), #Active bit --I remvoed #ID as it was never used
AS BEGIN
DECLARE #Schema sysname,
#Table sysname;
SET #Schema = CASE WHEN #Referral IN ('ReferralSource','ReferralSource2') THEN N'adm' END;
SET #Table = CASE #Referral WHEN 'ReferralSource' THEN N'JobReferralSource'
WHEN 'ReferralSource2' THEN N'JobReferralSource2' END;
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'UPDATE ' + QUOTENAME(#Schema) + N'.' + QUOTENAME(#Table) + NCHAR(13) + NCHAR(10) +
N'SET Active = #Active' + NCHAR(13) + NCHAR(10) +
N'WHERE Description= #Description;';
EXEC sp_executesql #SQL, N'#Description varchar(50), #Active bit', #Description = #Description, #Active = #Active;
END;

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.

Column name as a parameter in INSERT statement

I want to create one stored procedure where i want to insert values in a table.
But i don't know in which field i have to insert values and at the
runtime i will decide in which field the values should be inserted.
What i want to do is
insert into Tablename(#ColumnName, Description)
values (#ColumnValue, #MH_Description)
Can it possible that i pass this type of parameters in stored procedure as shown in the example above??
I want to use conditions too as
declare #Query nvarchar(4000)
declare #Query1 nvarchar(4000)
declare #ParmDefinition nvarchar(500);
set #Query = '
insert into tbl_temp(' + quotename(#ColumnName) +',Description)
values (#ColumnValue, #Description)'
set #Query1 = '
update tbl_temp set' + quotename(#ColumnName) +'=#ColumnValue, Description=#Description'
set #ParmDefinition = N'#ColumnValue varchar(100),#Description varchar(100)'
if exists(select 'true' from tbl_temp where quotename(#ColumnName)=#ColumnValue)
begin
exec sp_executesql #Query1, #ParmDefinition, #ColumnValue = #ColumnValue, #Description = #Description
end
else
begin exec sp_executesql #Query, #ParmDefinition, #ColumnValue= #ColumnValue, #Description = #Description
end
What am I doing wrong?
This is not possible to do with parameters. You will need to build dynamic query to achieve this.
The proc that uses dynamic SQL would look like this:
create procedure MyProc
(
#ColumnName varchar(100),
#ColumnValue varchar(100),
#MH_Description varchar(100)
)
as
begin
declare #Query nvarchar(4000)
declare #ParmDefinition nvarchar(500);
set #Query = '
insert into Tablename(' + quotename(#ColumnName) +',Description)
values (#ColumnValue, #MH_Description)'
set #ParmDefinition = N'#ColumnValue varchar(100), #MH_Description varchar(100)'
exec sp_executesql #Query, #ParmDefinition, #ColumnValue = #ColumnValue, #MH_Description = #MH_Description
end
[EDIT] Answer to your second question. Make it one query instead of two
set #Query = '
if exists(select * from tbl_temp where '+quotename(#ColumnName)+' = #ColumnValue)
update tbl_temp set' + quotename(#ColumnName) +' = #ColumnValue, Description=#Description
else
insert into tbl_temp(' + quotename(#ColumnName) +',Description)
values (#ColumnValue, #Description)'

Selecting from a table where the name is passed as a variable

I am trying to write a simple stored proc which takes three arguments 'database name one', 'database name two' and 'table name'. The sql will then perform a row count for the defined table in each database and store it.
Working on it piecemeal I have hit the first problem in that you can't do
select * from #tablename
I know you can use dynamic sql with the exec command but this is not ideal as I can't return values.
The following example looks like it should work but doesn't.
declare #tablename as nvarchar(500)
declare #sqlstring as nvarchar(500)
declare #parmdefinition as nvarchar(500)
declare #numrows as bigint
set #tablename = N'dummy_customer'
set #parmdefinition = N'#tablenameIN nvarchar(500), #numrowsOUT as bigint OUTPUT'
select #sqlstring = 'select #numrowsOUT = count(*) from #tablenameIN'
select #sqlstring
exec sp_executesql #sqlstring, #parmdefinition, #tablenameIN = #tablename, #numrowsOUT = #numrows OUTPUT
select #numrows
The error message given is
Msg 1087, Level 16, State 1, Line 1
Must declare the table variable "#tablenameIN".
Currently using SQL Server 2008 SP2.
Edit:
We're doing this because we are doing a migration and the customer wants a report which shows the row count for each table in the source and destination database. As there are many tables being able to use sp_MSForEachTable to call the stored proc seems ideal.
Edit:
The final solution for future reference is
declare #tablename as nvarchar(500)
declare #sqlstring as nvarchar(500)
declare #parmdefinition as nvarchar(500)
declare #numrows as bigint
set #tablename = N'dummy_customers'
set #parmdefinition = N'#tablename nvarchar(500), #numrowsOUT as bigint OUTPUT'
select #sqlstring = 'select #numrowsOUT = count(*) from ' + quotename(#tablename)
exec sp_executesql #sqlstring, #parmdefinition, #tablename = #tablename, #numrowsOUT = #numrows OUTPUT
select #numrows
You'd have to use dynamic sql, and concatenate the table name into the SQL string to then execute via sp_executsql:
select #sqlstring = 'select #numrowsOUT = count(*) from ' + QUOTENAME(#tablename)
EXECUTE sp_executesql ....

Is it possible to update a variable field in a stored procedure?

I have a simple query that updates a variable column. This query is later string.Formatted and passed to SqlCommand (c#) (TableId is a column in SomeTable):
"UPDATE SomeTable set {0}=#Value where TableId=#TableId"
I need to convert this query to a stored procedure. Is it possible at all?
It can only be done through dynamic SQL in the stored procedure (as Brad has alreayd answered, but of course you'd use QUOTENAME(#columnName) to properly protect against SQL injection, and use a sysname parameter type). If you go down that path, I recommend you read up first The Curse and Blessings of Dynamic SQL.
Updated:
I had to post code, since Brad's code has just too many mistakes.
create procedure myCustomUpdate
#columnName sysname,
#value sql_variant,
#id int
AS
declare #sql NVARCHAR(max);
set #sql = N'UPDATE SomeTable SET ' + QUOTENAME(#columnName)
+ N' = #value WHERE TableId = #id';
exec sp_executesql #sql, N'#value sql_variant, #id int', #value, #id;
I'm making a few assumptions about what you're doing, but evaluate this code.
CREATE PROCEDURE UpdateTableValue
(
#tableId INT
,#columnName sql_variant
,#value VARCHAR(10)
)
AS
BEGIN
DECLARE #sql NVARCHAR(MAX)
SET #sql = N'UPDATE SomeTable '
+ N'SET ' + QUOTENAME(#columnName) + N' = = #value '
+ N'WHERE TableId = #tableId'
EXEC sp_executesql #sql
, N'#value sql_variant, #tableId int'
, #value, #tableId;
END
I think there is a better solution in the article that Remus mentioned: The Curse and Blessings of Dynamic SQL
. I ended up using CASE switches to optimize performance (dynamic SQL is not being cached) like in the article:
UPDATE tbl
SET col1 = CASE #colname WHEN 'col1' THEN #value ELSE col1 END,
col2 = CASE #colname WHEN 'col2' THEN #value ELSE col2 END,