Issue with using like with a wildcard in dynamic SQL statement - sql

I am trying to use a dynamic SQL statement in a stored procedure to filter a query depending on the input from a user. I am starting basic with just one parameter that needs to find matching forenames in the database.
CREATE PROCEDURE dbo.uspFilter
#FirstName varchar(100) = null,
#Debug bit = 1
AS
DECLARE #SQL nvarchar(max);
DECLARE #Params nvarchar(max);
DECLARE #Search nvarchar(300);
SELECT #Params = N'#FirstName varchar(300) = null'
SELECT #SQL = N'SELECT * FROM Table WHERE 1=1'
IF #FirstName IS NOT NULL
SELECT #Search = N'#FirstName' + N'%'''
SELECT #SQL = #SQL + N' AND Forename LIKE ''' + #Search
IF #Debug = 1
#PRINT #SQL
EXEC sp_executeSQL #SQL, #Params, #FirstName = #FirstName;
GO
EXEC dbo.uspFilter #FirstName = 'Test', #Debug = 1;
GO
The output of the debug statement looks correct but it is not returning any results:
SELECT * FROM Table WHERE 1=1 AND Forename LIKE '#FirstName%'
EDIT: This is a meant to be a trimmed down version of what I am looking for - I will be using multiple parameters with different search criteria.

This is how you need to do it:
CREATE PROCEDURE dbo.uspFilter
#Search varchar(300) = null,
#Debug bit = 1
AS
DECLARE #SQL nvarchar(max);
DECLARE #Params nvarchar(max);
SELECT #Params = N'#FirstName varchar(300)';
SELECT #SQL = N'SELECT * FROM [Table]';
IF #FirstName IS NOT NULL BEGIN
SET #SQL = #SQL + N' WHERE Forename LIKE #FirstName + ''%'';';
END ELSE BEGIN
SET #SQL = #SQL + N';';
END
IF #Debug = 1
PRINT #SQL;
EXEC sp_executeSQL #SQL, #Params, #FirstName = #Search;
GO
EXEC dbo.uspFilter #Search = 'Steve', #Debug = 1;
Note that although Crowcoder's comment about SQL Injection is important, you can write dynamic SQL that doesn't allow for Injection. Such as the above.
Edit: Couple of slight corrections.

CREATE PROCEDURE dbo.uspFilter
#Search varchar(300) = null,
#Debug bit = 1
AS
DECLARE #SQL nvarchar(max);
DECLARE #Params nvarchar(max);
SELECT #Params = N'#FirstName varchar(300)';
SELECT #SQL = N'SELECT * FROM [YourTable]';
IF #Search IS NOT NULL BEGIN
SET #SQL = #SQL + N' WHERE Forename LIKE #FirstName;';
END ELSE BEGIN
SET #SQL = #SQL + N';';
END
IF #Debug = 1
PRINT #SQL;
EXEC sp_executeSQL #SQL, #Params, #FirstName = #Search;
GO
EXEC dbo.uspFilter #Search = 'Rameshbhai%', #Debug = 1;
go
pass parameter with wildcard values like 'searchtext%'

You can also apply dynamic where condition as below without make dynamic query, please have look at below sample example. You can join other filter by applying AND where clause with below example.
Declare #firstName VARCHAR(50)='sa'
SELECT
*
FROM Table
WHERE
(
(Forename LIKE '%'+#firstName+'%' AND #firstName!='')
OR
(#firstName='')
)

Related

How to get value by dynamic field Name using sql select query

I am passing dynamic column name base that column name to get the value and below i my table
Table_CandidateInfo
Id Name Age City
1 Mazhar 30 Gulbarga
20 Khan 29 Bidar
Example1
Declare #ColumnName varchar(100), #Id int
set #ColumnName='Age'
set #Id=20
select * from Table_CandidateInfo where ID=#Id and
I am not able to pass ColumnName with and query because column name is dynamic pass by code. My output should be
29
Example2: If my #ColumnName='City' and #Id=20 then output should be like below
Bidar
I think what you are actually after is the below:
DECLARE #ColumnName sysname, #Id int;
SET #Id = 29;
SET #ColumnName = N'Age';
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'SELECT ' + QUOTENAME(#ColumnName) + N' FROM dbo.Table_CandidateInfo WHERE Id = #Id;';
--PRINT #SQL; --Your debugging friend
EXEC sp_executesql #SQL, N'#Id int', #Id = #Id;
Alas, you cannot pass identifiers as parameters. You need to use dynamic SQL:
declare #columnName varchar(100);
declare #Id int;
set #ColumnName = 'Age' ;
set #Id = 20;
declare #sql nvarchar(max);
set #sql = '
select *
from Table_CandidateInfo
where [columnName] = #Id';
select #sql = replace(#sql, '[columnName]', quotename(#columnName));
exec sp_executesql #sql,
N'#id int',
#id=#id;

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.

Get SQL result into variable

Here is what I have so far. I want to store the query result into an int variable but I get null so far. After execute, both #query and #countrow are both null. Any suggestion would be great.
SET #query = N'select #countrow=count(*) from ' + #tablename
EXECUTE sp_executesql #query
DECLARE #i INT, #sql NVARCHAR(512), #tablename varchar(200) = 'tbl'
SET #sql = N'SELECT #i = COUNT(*) FROM ' + #tablename
EXEC sp_executesql
#query = #sql,
#params = N'#i INT OUTPUT',
#i = #i OUTPUT
PRINT #i
Take a look at SQL Fiddle
You need to use the OUTPUT keyword, similar to this:
declare #query nvarchar(max)
declare #countrow int
declare #tablename varchar(50)
SET #query = N'select #cnt=count(*) from ' + #tablename
EXECUTE sp_executesql #query, N'#cnt int OUTPUT', #cnt=#countrow OUTPUT
select #countrow as countrow -- to get the result

How to return the rowcount of a dynamic sql query in SP?

I would like to return the rowcount of a dynamic sql query using linq similar to mentioned here:
http://weblogs.asp.net/scottgu/archive/2007/08/16/linq-to-sql-part-6-retrieving-data-using-stored-procedures.aspx
I'm using dynamic sql to create the where clause and to implement paging on the result set, The rowcount I want to return is the total number of records that meet the where condition.
My SQL that is causing me problems:
-- get row count
SET #SQL = '#TotalRowCount = SELECT COUNT(*) as TotalRowCount'
SET #SQL = #SQL + #WHERE
IF (LEN(#SUBWHERE) > 0)
BEGIN
SET #SQL = #SQL + #SUBWHERE
END
SET #SQL = #SQL + ')) '
exec sp_executesql #SQL
END
(I need this to be the output param #TotalRowCount in the param list here):
ALTER PROCEDURE [dbo].[_tournament_GetTournamentsByConveners]
(
#LastName varchar(100) = null ,
#Username varchar(256) = null ,
#Email varchar(100) = null ,
#IsWildcard bit = null,
#PageIndex int ,
#PageSize int,
#TotalRowCount int output
)
AS
This is by design.
The scope of the #TotalRowCount in the dynamic SQL is different to the scope of #TotalRowCount declared in the stored procedure. That is, the dynamic SQL has it's own scope.
If you insist on using dynamic SQL, do this to add the total rows to the record set that is returned
SELECT col1, col2,
COUNT(*) OVER () AS TotalRows
FROM ...
Otherwise we only have partial code to offer any suggestions for improvement. You appear to have LINQ to call stored procs with execute dynamic SQL. This is too convoluted.
You can declare output parameters with sp_executesql. So to get your result alter the code as shown below.
Always be careful when concatenating SQL code like this as it is extremely vulnerable to SQL injection.
DECLARE #SQL nvarchar(4000)
SET #SQL = N'SELECT #TotalRowCount = COUNT(*) as TotalRowCount'
SET #SQL = #SQL + #WHERE
IF (LEN(#SUBWHERE) > 0)
BEGIN
SET #SQL = #SQL + #SUBWHERE
END
SET #SQL = #SQL + N')) '
exec sp_executesql #SQL, N'#TotalRowCount int output', #TotalRowCount output
you can also express this more compact as:
DECLARE #SQL nvarchar(4000)
SET #SQL = N'SELECT #TotalRowCount = COUNT(*) as TotalRowCount' + #WHERE + ISNULL(#SUBWHERE, N'') + N'))'
exec sp_executesql #SQL, N'#TotalRowCount int output', #TotalRowCount output

Dynamic sql statement to update a variable

my sql statement is something like this below
DECLARE #OLD_NAV_VALUE AS INT
DECLARE #FINAL AS INT
SELECT #OLD_NAV_VALUE = [col1] from TBL_BA where DATE = #id_Date
SET #FINAL = #OLD_NAV_VALUE * 50
But the problem i am haveing here is that the column name in the select statement which is given as [col1] is a dynamic value. So i am trying something like this below.
DECLARE #OLD_NAV_VALUE AS INT
DECLARE #FINAL AS INT
EXEC('SELECT #OLD_NAV_VALUE = [' + #DYNAMIC_COL_NAME + '] from TBL_BA where DATE = ' + #id_Date)
SET #FINAL = #OLD_NAV_VALUE * 50
this gives an error that #OLD_NAV_VALUE has to be declared. So i tried declaring #OLD_NAV_VALUE inside the EXEC statement. But if i do this i am not able to use the same outside the EXEC statement.
Please let me know how to do this.
You can also use the sp_executesql statement with an output parameter:
declare #field nvarchar(50);
set #field = N'FieldToSelect';
declare #sql nvarchar(3000);
declare #parmDefinition nvarchar(500);
SET #parmDefinition = N'#returnValueOUT nvarchar(50) OUTPUT';
set #sql = N'SELECT #ReturnValueOUT = ' + #Field + ' FROM [TableName] WHERE [SomeCondition]'
declare #returnValue nvarchar(50);
EXECUTE sp_executesql #sql, #parmDefinition, #returnValueOut = #returnValue OUTPUT;
SELECT #returnValue
First, I'd suggest that you do a Google on "Erland dynamic SQL" and read his white paper on the subject.
Your design is probably not the best if it requires that you use a dynamic column name like this.
The reason that you can't do what you're trying to do is that everything in the EXEC is entirely in its own scope. If you absolutely have to do it this way though then you could use a table (either a normal table, or a global temporary table) to store the value for use outside of the EXEC.
We've used sp_executesql. Here's another example of a parameterized record count:
DECLARE #sql AS nvarchar(MAX)
SET #sql = N'SELECT #RecordCount = COUNT(*) FROM [{#SchemaName}].[{#TableName}]'
SET #sql = REPLACE(#sql, '{#SchemaName}', #SchemaName)
SET #sql = REPLACE(#sql, '{#TableName}', #TableName)
DECLARE #RecordCount AS int
EXEC sp_executesql
#query = #sql,
#params = N'#RecordCount INT OUTPUT',
#RecordCount = #RecordCount OUTPUT
This worked for me.
I declared a temp table and used it to receive the values from the select statement.
Something like below.
declare #i int
declare #v int
create table #t (val int)
insert into #t
exec ('declare #i int set #i = 0 select #i+1')
select * from #t