Error when declaring parameter in stored procedure - sql

I am trying to create a stored procedure that utilizes a variable number of parameters. As I am pretty new to writing stored procedures and TSQL in general, I decided to try and write it with only one parameter. However, I keep getting an error stating "Must declare scalar variable #FirstName" when I try to execute it. Right now, I am trying to store the SQL statement in another variable, #sql. My procedure looks like this:
ALTER PROCEDURE [dbo].[GetEmployeeByParameters]
(#FirstName varchar(50))
AS
BEGIN
DECLARE #sql AS NVARCHAR(4000)
SET #sql = 'SELECT e.* from Employee e
WHERE e.FirstName = #FirstName'
EXEC (#sql)
END
I've looked elsewhere and tried EXEC sp_execute #sql which didn't work. Strangely, what does work is when I don't declare the #sql variable and instead just write my query normally. Since that is the case, I'm assuming there is some error in my usage of the SET and EXEC functions. I'm also not 100% sure that I'm using BEGIN and END properly. The way I understood it is that BEGIN and END separate SQL statements into logical blocks, and thus are more commonly used when IF comes into play. Could anyone tell me what exactly is going on with my parameter here? It's really confusing me as to why SQL Server thinks it's not declared.

The variable parameter needs to be outside the quotes.
SET #sql = N'SELECT e.* from Employee e
WHERE e.FirstName = ''' + #FirstName + ''''
Or, better yet, run it without any dynamic SQL.
SELECT e.*
from Employee e
WHERE e.FirstName = #FirstName

Because
'Select ... #FirstName'
is not the same as
Select ... #FirstName
One is a string and the other is a SQL Query
What you should do instead is
ALTER PROCEDURE [dbo].[GetEmployeeByParameters]
(#FirstName varchar(50))
AS
BEGIN
DECLARE #sql AS NVARCHAR(4000)
SET #sql = 'SELECT e.* from Employee e
WHERE e.FirstName = ''' + #FirstName + ''''
EXEC (#sql)
END

When you execute dynamic sql, you are switching contexts and variables don't move between contexts. Once you declare the SQL statement as a string, everythign must be provided to that string in order for it to recognize it.
Obviously, you don't need dynamic SQL in this case, but once method of doing it is like so:
ALTER PROCEDURE [dbo].[GetEmployeeByParameters]
(#FirstName varchar(50))
AS
BEGIN
DECLARE #sql AS NVARCHAR(4000)
SET #sql = 'SELECT e.* from Employee e
WHERE e.FirstName = #FirstName'
EXEC sp_executeSQL #sql, N'#Firstname varchar(50)', #FirstName
END
sp_executeSQL allows you to declare internal parameters (the second clause), and supply them with values (the last clause).

You need to change your query to the following as the #Firstname variable is not in scope:
ALTER PROCEDURE [dbo].[GetEmployeeByParameters]
(#FirstName varchar(50))
AS
BEGIN
DECLARE #sql AS NVARCHAR(4000)
SET #sql = 'SELECT e.* from Employee e
WHERE e.FirstName = ''' + #FirstName + ''''
EXEC (#sql)
END

As this is a "search" type query your doing with a variable number of params, you need to build the string up bit by bit -- you're on the right lines that it needs to be dynamic, but you also need to avoid SQL injection attacks (google "Little Bobby Tables"). Thus you need to use a parameterised dynamic SQL statement:
ALTER PROCEDURE [dbo].[GetEmployeeByParameters]
#FirstName VARCHAR(50)
AS
BEGIN
DECLARE #sql AS NVARCHAR(4000)
SET #sql = 'SELECT e.* FROM Employee e WHERE 1 = 1'
IF #FirstName IS NOT NULL
BEGIN
SET #sql = #sql + ' AND FirstName = #pFirstName'
END
-- More IF statements, building up the query
EXEC sp_ExecuteSQL #sql, N'#pFirstName VARCHAR(50)', #FirstName
The second and third params map the #FirstName parameter to the query's "internal" parameters (which I normally prefix with a 'p' or 'param' just to differentiate them from the stored procedure's own parameters).
You extend the sp_Exceute as appropriate each time you add a new parameter to search by, so you might end up doing:
EXEC sp_ExecuteSQL #sql,' N'
#pFirstName VARCHAR(50),
#pSurName VARCHAR(50),
#pDateOfBirth DATETIME', #FirstName, #Surname, #DateOfBirth

Use this body, without "dynamic" query
ALTER PROCEDURE [dbo].[GetEmployeeByParameters]
(#FirstName varchar(50))
AS
BEGIN
SELECT e.* from Employee e
WHERE e.FirstName = #FirstName
END
OR
using dynamic query do not include variables itselves into the script - do concatenate them with the script and do not forget to properly quote them.

Related

EXEC sp_executesql will work with Integers but not VarChars

I'm using EXEC sp_executesql for a dynamic query in SQL Server 2017.
I've tried various testing scenarios, and I can get results in my query (for other parameters) as long as the values passed in are Integers. So, that means, Location and Department testing works. However, I can't figure out if there's something I need to do differently for when I'm sending a NVARCHAR or DateTime.
Here's my stored procedure, with the NVARCHAR param. Do you see anything I'm doing wrong?
(
#tktitle NVARCHAR(200)
)
AS
BEGIN
Declare #SQL NVARCHAR(MAX)
Set #SQL = 'SELECT timekeep.tkinit, timekeep.tkfirst, timekeep.tklast,
timekeep.tkemdate, timekeep.tktitle, timekeep.tkloc, timekeep.tkdept
FROM abc.xyz'
IF #tktitle IS NOT NULL
Select #SQL = #SQL + 'AND ([tktitle] = #tktitle)'
EXEC sp_executesql #SQL, N'#tktitle varchar', #tktitle
END
I can identify at least three issues:
You need to specify a length for varchar when passing it as a parameter.
You also need a space before the AND and the AND should be a WHERE.
You need to assign the parameter in the execute call.
So:
IF #tktitle IS NOT NULL
Select #SQL = #SQL + ' WHERE ([tktitle] = #tktitle)';
-------------------------^ separator
EXEC sp_executesql #SQL, N'#tktitle varchar(200)', #tktitle=#tktitle;

Select into tables dynamically with variables

I have some code to create tables based on a set of dates I define.
Example, I have 5 dates, and they are aren't consecutive. For any of these dates, I want to create a table and I am currently using a Select into.
I am having to do this 5 times, even though the only thing changing is the name of the new table created and the date. Is there a way to do this in an elegant way.
I started writing some code, but I am struggling to get it to loop through all the dates I want. The way I have written it currently, I only works if I edit the date at the start.
DECLARE #MyDate DATE;
SET #MyDate = '2019-01-01';
SET #TableName = 'Table1';
SELECT *
into #TableName
FROM Original_Table
WHERE Query_Date = #MyDate;
Is this a one time thing or do you have to do this on a regular basis?
If it's the first, than I would just do it and get it over with.
If it's the latter, then I suspect something is very wrong with the way that system is designed - but assuming that can't be changed, you can create a stored procedure that will do this using dynamic SQL.
Something like this can get you started:
CREATE PROCEDURE dbo.CreateTableBasedOnDate
(
#MyDate DATE,
-- sysname is a system data type for identifiers: a non-nullable nvarchar(128).
#TableName sysname
)
AS
-- 200 is long enough. Yes, I did the math.
DECLARE #Sql nvarchar(200) =
-- Note: I'm not convinced that quotename is enough to protect you from sql injection.
-- you should be very careful with what user is allowed to execute this procedure.
N'SELECT * into '+ QUOTENAME(#TableName) +N'
FROM Original_Table
WHERE Query_Date = #MyDate;';
-- When dealing with dynamic SQL, Print is your best friend.
-- Remark this row and unremark the next only once you've verified you get the correct SQL
PRINT #SQL;
--EXEC sp_ExecuteSql #Sql, N'#MyDate Date', #MyDate
GO
Usage:
EXEC CreateTableBasedOnDate '2018-01-01', 'zohar';
Use dynamic SQL:
DECLARE #MyDate DATE, #TableName varchar(50);
SET #MyDate = '2019-01-01';
SET #TableName = 'Table1';
DECLARE #sql NVARCHAR(4000);
DECLARE #params NVARCHAR(4000);
SELECT #sql=N'
SELECT *
INTO ' + QUOTENAME(#TableName) + '
FROM Original_Table
WHERE Query_Date = #MyDate;';
SELECT #params = N'#MyDate DATE';
EXEC sys.sp_executesql #sql, #params, #MyDate=#MyDate
Note that dynamic SQL can be dangerous as it opens up a path for SQL injection. Its fine if you are just using it in your own local scripts, but take care if you e.g. wrap this in a procedure that is more widely accessible.
I would use dynamic SQL although I would add another variables for the schema:
DECLARE
#MyDate nVarchar(50) = '2019-01-01',
#Schema nVarchar (50) = 'dbo',
#TableName nVarchar(250) = 'Table1',
#SQL nVarchar(500);
Set #SQL = '
SELECT *
into '+ QUOTENAME(#Schema)+'.'+ QUOTENAME(#TableName) +'
FROM Original_Table
WHERE Query_Date = '+ #MyDate +';
'
--print #SQL
Exec(#SQL)
You can use the print statement to see how the SQL will look before executing this properly. You may also want to look at adding this as a stored procedure.

SQL Injection attack with stored procedures [duplicate]

This question already has answers here:
How to cleanse (prevent SQL injection) dynamic SQL in SQL Server?
(8 answers)
Closed 3 years ago.
I have a stored procedure as below:
CREATE PROCEDURE sp_GetName(#Name NVARCHAR(50))
AS
BEGIN
DECLARE #sqlcmd NVARCHAR(MAX);
DECLARE #params NVARCHAR(MAX);
SET #sqlcmd = N'SELECT * FROM [dbo].[Employee] WHERE Name like ''%' + #Name + '%''';
PRINT #sqlcmd;
SET #params = N'#Name NVARCHAR(50)';
EXECUTE sp_executesql #sqlcmd, #params, #Name;
END
EXEC sp_GetName '';WAITFOR DELAY '00:00:10'
Whenever I execute above statement, it always delays the response.
How can I write my procedure so that it will handle this SQL Injection attack.
Don't use dynamic SQL if you want to avoid SQL injection attacks. It doesn't matter whether you wrap the string concatenation code in a stored procedure or not.
In this case there's no reason to use dynamic SQL. You can simply write :
SELECT * FROM [dbo].[Employee] WHERE Name like '%' + #Name + '%'
to search for employees with a specific string in their name.
This query is immune to SQL injection whether you execute it as a parameterized query from a client or as a stored procedure. Clients would call both of them the same way.
The stored procedure should be just :
CREATE PROCEDURE getName(#Name nvarchar(50)
AS
SELECT * FROM [dbo].[Employee] WHERE Name like '%' + #Name + '%'
I removed the sp_ prefix because it's used for system stored procedures.
The problem with this code is that %something% will have to search the entire table. It can't use any index on Name. Only prefix searches can use indexes, ie #Name + '%' :
SELECT * FROM [dbo].[Employee] WHERE Name like #Name + '%'
You just need to parametrize the dynamic code.
CREATE PROCEDURE sp_GetName(#Name NVARCHAR(50))
AS
BEGIN
DECLARE #sqlcmd NVARCHAR(MAX);
DECLARE #params NVARCHAR(MAX);
SET #sqlcmd = N'SELECT * FROM [dbo].[Employee] WHERE Name like ''%'' + #Name + ''%''';
print #sqlcmd;
SET #params = N'#Name NVARCHAR(50)';
EXECUTE sp_executesql #sqlcmd, #params, #Name;
End
exec sp_GetName '';
However, the code has nothing that would justify the need of making it dynamic. It could be simply rewritten as this.
CREATE PROCEDURE sp_GetName(#Name NVARCHAR(50))
AS
SELECT * FROM [dbo].[Employee] WHERE Name like '%' + #Name + '%';
Why are you making your application slow by design?
You can just embed the SQL in you're stored procedure without using sp_executesql
CREATE PROCEDURE sp_GetName(#Name NVARCHAR(50))
AS
BEGIN
SELECT * FROM [dbo].[Employee] WHERE Name like '%' + #Name + '%'
End
Your example doesn't actually demonstrate a SQL injection attack; you've just got 2 sql statements seperated by a semi-colon, the second of which is a "wait"
-- Also demonstrates wait behaviour
exec sp_who;WAITFOR DELAY '00:00:10'

Dynamic SQL with parameters

I have a SQL query stored in a table that contains parameter names. I need to know how to execute it properly in a stored procedure.
This is my SQL code in the procedure
PROCEDURE [spMassUpdateSKUs]
#SKU AS NVARCHAR(20)
,#FIELD AS NVARCHAR(50)
,#VALUE AS NVARCHAR(50)
,#RESULT as Int = Null Output
AS
BEGIN
IF EXISTS(SELECT CODENUMBER FROM INVENTORY_MASTER WHERE CODENUMBER=#SKU)
BEGIN
DECLARE #SQLQUERY AS NVARCHAR(50)
SET #SQLQUERY=(SELECT SQLUPDATE FROM MASS_UPDATE WHERE DROPDOWNLABEL=#FIELD)
EXEC SP_EXECUTESQL #SQLQUERY
END
and this is the sql query from the table
update inventory_master_flex set departmentid=#value where codenumber=#sku
I've tried replacing with the real parameters but that doen't work.
SELECT REPLACE(REPLACE(#SQLQUERY,'#VALUE',#VALUE),'#SKU',#SKU)
-- 50 is too short for sure; you may try 1000 or different number
DECLARE #SQLQUERY AS NVARCHAR(MAX)
-- for debug purpose
PRINT #SQLQUERY
-- params
EXEC SP_EXECUTESQL #SQLQUERY, N'#Value NVARCHAR(50), #sku NVARCHAR(50)`, #Value, #sku
REPLACE is not good in case of strings with quotes and so on which would brake the #sqlquery code.
Pass the parameters in using sp_executesql, not replace():
IF EXISTS(SELECT CODENUMBER FROM INVENTORY_MASTER WHERE CODENUMBER=#SKU)
BEGIN
DECLARE #SQLQUERY AS NVARCHAR(MAX);
SET #SQLQUERY = (SELECT SQLUPDATE FROM MASS_UPDATE WHERE DROPDOWNLABEL = #FIELD);
EXEC SP_EXECUTESQL #SQLQUERY, N'#SKU VARCHAR(255), #VALUE VARCHAR(255)', #SKU = #SKU, #VALUE = #VALUE
END;
I don't know what the types are. But if one or both are strings or dates, then you would need single quotes in your implementation. However, you are already using sp_executesql so go whole-hog and pass in parameters as well.

Execute sp_executesql, Table Variabe not Declared

I am Using SQL server 2012 and i want to select random columns from my table by applying where condition in this query:
EXECUTE sp_executesql
N'SELECT *
FROM #table
WHERE #Col = #Value',
N'#Value nvarchar(44),#table nvarchar(55),#Col nvarchar(30)',
#Value = 'Cus_1',#Col='CustId',#table='SaleOrder';
But when I execute it, it shows error
Must declare the table variable "#table"
I also tried it to declare by this: #table table(Id nvarchar(30)), but thin it shows again an error on table type...
Please help
This is what you are trying to run:
EXECUTE sp_executesql
N'SELECT * FROM #table WHERE #Col = #Value',
N'#Value nvarchar(44), #table nvarchar(55), #Col nvarchar(30)',
#Value = 'Cus_1', #Col='CustId', #table='SaleOrder';
Alas. You cannot substitute in a table name or column name using parameter substitution. So, SQL Server is looking for a table variable called #table. You can fix this by putting the values directly into the string:
declare #Col = 'CustId', #table = 'SaleOrder';
declare #sql nvarchar(max) = N'SELECT * FROM ' + #table + ' WHERE ' + #Col + ' = #Value';
EXECUTE sp_executesql #sql,
N'#Value nvarchar(44)',
#Value = 'Cus_1';
Unfortunately, I cannot find a good reference in the documentation that explains what is happening. When a statement is compiled, it is allowed to have parameters. However, the parameters are for values in the statement, not for column, table, database, or UDF names or for keywords. The statement itself is compiled, with place holders for the parameters, and in order to be compiled, the SQL engine needs to resolve all object names.