Building dynamic SQL command - Incorrect syntax error - sql

I want to build a SQL command dynamically by checking if a parameter is null. If it is not null, it will be added to the command string. The stored procedure can be compiled, but at running time I get an invalid syntax error. Here is the code I've written so far:
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 = N'#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
The query I execute:
EXEC searchEvents 'Wine fair', NULL, NULL, NULL, NULL, NULL
The error I get:
Incorrect syntax near "Wine fair".

Add a PRINT statement for debugging and the reason for the error will be clear:
WHERE Event.Name LIKE %Wine fair%
As you can see, the single-quotes around the intended literal are missing.
Note that this code is vulnerable to SQL injection. If you only add the missing quotes and use string concatenation, malicious SQL can be injected. Instead, specify the parameter value directly in the generated SQL statement rather than a string literal. The example below also escapes LIKE wildcards so that the desired results are returned even if the user-supplied value contains them.
SET #name = REPLACE(#name,'%', '[%]'); --escape % wildcard
SET #name = REPLACE(#name,'_', '[_]'); --escape _ wildcard
SET #name = REPLACE(#name,'[', '[[]'); --escape [ wildcard
SET #name = '%' + #name + '%' --add desired wildcards
SET #sqlCommand = #sqlCommand + 'Event.Name LIKE #name'; --use parameter directly in SQL statement
Also make sure the application runs under a minimally-privileged account. For maximum security, use sign the stored procedure with a certificate associated with a user with the needed SELECT permissions. That way, the app only needs execute permissions on the proc.

You missed quotes
SET #sqlCommand = #sqlCommand + 'Event.Name LIKE ' + '''%' + #name + '%'''
UPD:
To avoid SQL injection:
SET #sqlCommand = #sqlCommand + 'Event.Name LIKE ''%'' + #p_name + ''%'''

Related

Like in dynamic function

The code below works well. I however have issues trying to turn it into a like statement that I need some assistance with
CREATE PROCEDURE [dbo].[searcher]
#deliverer nvarchar (100)
AS
BEGIN
DECLARE #sql nvarchar(1000)
SET #sql = 'SELECT location, deliverer, charger FROM Store where 1=1'
IF (#deliverer IS NOT NULL)
SET #sql = #sql + ' and deliverer =#pt'
DECLARE #t1 as TABLE
(
location varchar(1000),
deliverer varchar(100),
charger varchar(100)
)
INSERT INTO t1
EXEC sp_executesql #sql,
N'#pt nvarchar(100)',
#pt=location
SELECT * FROM t1
END
So far, I have tried the code below but with not much success
DECLARE #pt nvarchar (100)
SET #pt = '%' + #pt + '%'
IF (#deliverer IS NOT NULL)
SET #sql = #sql + ' and deliverer like #pt'
I have also tried;
DECLARE #pt nvarchar (100)
IF (#deliverer IS NOT NULL)
SET #sql = #sql + ' and deliverer like ''% + #pt + %'''
If your stored procedure parameter is #deliverer and your dynamic SQL parameter is #pt, I believe your sp_executesql execution should assign the parameter as #pt = #deliverer.
As for adding wildcards, you can either add them before the call with
SET #deliverer = '%' + #deliverer + '%'
or add them in the dynamic SQL with
SET #sql = #sql + ' and deliverer like ''%'' + #pt + ''%'''
Note the doubled up quotes around the %. The variable #pt is not quoted

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.

sql Append for value with single quotes - String Concatenation

This extended version on my previous question sql Append for value with single quotes which am trying to fix
DECLARE #fNAME varchar(40) = 'O'brain'
DECLARE #query nvarchar(256)
DECLARE #id nvarchar(5) = '8'
SET #query = 'UPDATE TableName SET columnname = ''' + #fNAME + '''' +'Added on '+ GETDATE() + ''' WHERE Id= ' + convert(nvarchar, #id)
am trying to get below output using
EXEC sp_executesql
Update table name set columnname = 'O''brain Added on Aug 8 2017 11:15AM' where id = 8
Basically am trying to append some text and current date along with name to be updates as text in one column
I would define and execute the query as:
DECLARE #fNAME varchar(40) = 'O''brain';
DECLARE #query nvarchar(256);
DECLARE #id nvarchar(5) = '8';
SET #query = '
UPDATE TableName
SET columnname = #fNAME + '' added on '' + convert(varchar(255), GETDATE() )
WHERE Id = #id';
EXEC sp_executesql #query, N'#fname varchar(40), #id nvarchar(5)', #fname=#fname, #id=#id;
Note: This is not going to format the date exactly as in your question. You haven't chosen a formatting for the date in your code, so I didn't either.
Here is how you would do this with a normal update. There appears to be no reason at all to use dynamic sql here.
DECLARE #fNAME varchar(40) = 'O''brain';
DECLARE #id nvarchar(5) = '8';
Update TableName
set columnname = #fNAME + ' added on ' + convert(varchar(255), getdate())

Single quote in custom query

I have this procedure for custom paging, search and sort options.
ALTER PROCEDURE [dbo].[stp_OrdersPaginated]
#Name NVARCHAR(50)=NULL,
#OrderNumber NVARCHAR(50)=NULL,
#Status NVARCHAR(50)=NULL,
#OrderBy NVARCHAR(100)=NULL,
#PageNumber INT,
#PageSize INT
AS
BEGIN
SET NOCOUNT ON;
CREATE TABLE #ORDERS
(
[OrderId] Bigint
,[Name] Varchar(100)
,[Status] Varchar(50)
,[CreatedDate] Date
,[OrderNumber] Varchar(100)
,[UserId] Bigint
,[Amount] Decimal
,RowNumber Bigint IDENTITY(1,1)
)
DECLARE #intTotal INT
SET #intTotal = #PageSize * #PageNumber
DECLARE #sSQL NVARCHAR(MAX)
DECLARE #Where NVARCHAR(MAX) = ''
DECLARE #Order NVARCHAR(MAX) = ''
SET #sSQL = 'SELECT dbo.[Order].OrderId, [User].Name, dbo.[Order].Status,
dbo.[Order].CreatedDate, [Order].OrderNumber, dbo.[User].UserId,
dbo.Order.[Amount]
FROM dbo.[Order]
INNER JOIN dbo.User
ON dbo.[User].UserId = dbo.[Order].UserId'
SET #Order =' ORDER BY ' +#OrderBy
IF #Name is not null
SET #Where = #Where + ' AND dbo.[User].Name LIKE ''%'+#Name+'%'''
IF #OrderNumber is not null
SET #Where = #Where + ' AND dbo.[Order].OrderNumber LIKE '''+#OrderNumber+'%'''
IF #Status is not null
SET #Where = #Where + ' AND dbo.[Order].[Status] LIKE '''+#Status+'%'''
IF LEN(#Where) > 0
SET #sSQL = #sSQL + ' WHERE ' + RIGHT(#Where, LEN(#Where)-4)
INSERT INTO #ORDERS
EXECUTE (#sSQL + #Order)
Select [OrderId],[Name],[Status],[CreatedDate],[OrderNumber,[UserId]
,[Amount],RowNumber
From #ORDERS
WHERE RowNumber between ((#PageNumber * #PageSize)-(#PageSize- 1)) AND (#PageNumber * #PageSize)
Declare #TotalRecords Integer
Declare #TotalPage Integer
SELECT #TotalRecords=MAX(RowNumber) from #ORDERS
if(#TotalRecords is not NULL)
begin
if(#TotalRecords%#PageSize = 0)
begin
SET #TotalPage = #TotalRecords/#PageSize
end
else
begin
SET #TotalPage = #TotalRecords/#PageSize + 1
end
end
else
begin
set #TotalPage = 1
end
Select #TotalPage [TotalPages], #TotalRecords [TotalRecords]
DROP Table #ORDERS
END
As you can see one of the Search params is Name. The Procedure works perfectly for all except for Single Quote(') for obvious reason. Example: if I pass O' Brien for name it would fail. Is there any way to handle such single quote values with custom queries on SQL Server?
Your problem stems from not constructing your dynamic SQL in a best-practice manner, which along with making it difficult to construct the correct SQL, is also exposing you to SQL injection attacks.
Essentially, you should never use concatenation when adding parameters to your SQL string. I also use char(37) to represent the % sign, as this way it isn't necessary to escape it with apostrophes.
So your SQL becomes something like
IF #Name is not null
SET #Where += 'AND Name LIKE char(37)+#Name+char(37)'
IF #OrderNumber is not null
SET #Where += ' AND OrderNumber LIKE #OrderNumber+char(37)'
IF #Status is not null
SET #Where += ' AND [Status] LIKE #Status+char(37)'
IF LEN(#Where) > 0
SET #sSQL += ' WHERE ' + RIGHT(#Where, LEN(#Where)-4)
Creating the OrderBy is harder as you cannot parameterise that. if you absolutely trust the value passed in, then your code is okay, but the safest way would be to have something like an if statement that
tests the value passed in and creates the appropriate clause. e.g.
IF #OrderBy = 'status'
SET #Ssql += ' ORDER BY Status'
--Next you need to declare the parameters being included in the dynamic SQL. i'm making up the variable types, as you didn't specify what they were.
declare #params nvarchar(1000) = '#name nvarchar(100), #ordernumber nvarchar(100), #status nvarchar(10)'
--Then you can execute your dynamic SQL, passing to it the parameters provided to your procedure
insert into #temp
ExeCUTE sp_executesql #sSQL, #params, #name, #ordernumber, #status
One other benefit of constructing dynamic SQL in this manner, rather than concatenating strings, is that SQL Server can actually cache the query plan like it does for non-dynamic SQL and you don't have the performance hit that you get when you use concatenation.
Try:
IF #Name is not null
BEGIN
SET #Name = REPLACE(#Name, '''', '''''')
SET #Where = #Where + ' AND dbo.[User].Name LIKE ''%'+#Name+'%'''
END

Declare and concatenate a variable in sql

so i want to pass several values to a stored procedure. then bases on those values, add to a variable that would then be set as my where clause. but im stumped and google aint helping. here is what i have/my idea
CREATE PROCEDURE sp_RunReport #TransType varchar(255), #Accounts varchar(75)
AS
--declare a varchar WHERE clause variable here
IF #TransType <> ''
--add to WHERE clause variable
iF #Accounts <>''
--add to WHERE clause variable
SELECT *
FROM log
WHERE --my WHERE clause
GO
i dont see how this is possible. i can do it all in c sharp in the front end, but i feel like it should be done and can be done in the stored procedure. any help would be greatly appreciated
While dynamic SQL in previous answer is perhaps fine, I would suggest this "pure" SQL approach
WHERE TransType = ISNULL(#TransType, TransType)
AND Accounts = ISNULL(#Accounts, Accounts)
There is some room to optimize performance by getting rid of ISULL (not optimal when used in WHERE clause), but this should give you the idea.
Of course, insteead of AND your logic may reuire an OR and also you need to ensure that params are "properly" empty (NULL in my case or whatever will constitute "empty" if you re-write this to remove the ISNULL)
You can use dynamic SQL using EXEC:
CREATE PROCEDURE sp_RunReport #TransType varchar(255), #Accounts varchar(75)
AS
DECLARE #where VARCHAR(4000) = ' WHERE 1=1'
DECLARE #sql NVARCHAR(4000)
IF #TransType <> ''
SET #where = #where + ' AND TransType = ''' + #TransType + ''''
IF #Accounts <>''
SET #where = #where + ' AND Accounts = ''' + #Accounts + ''''
SET #sql = 'SELECT *
FROM log' + #where
exec (#sql)
Or using sp_executesql which is recommended:
CREATE PROCEDURE sp_RunReport #TransType varchar(255), #Accounts varchar(75)
AS
DECLARE #where NVARCHAR(4000) = N' WHERE 1=1'
DECLARE #sql NVARCHAR(4000)
DECLARE #ParmDefinition nvarchar(500) = N'#TransType varchar(255), #Accounts varchar(75)'
IF #TransType <> ''
SET #where = #where + N' AND TransType = #TransType'
IF #Accounts <>''
SET #where = #where + N' AND Accounts = #Accounts'
SET #sql = N'SELECT *
FROM log' + #where
EXECUTE sp_executesql #sql, #ParmDefinition,
#TransType = #TransType, #Accounts = #Accounts