Declare and concatenate a variable in sql - 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

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.

Dynamic Stored Procedure with View name and Date as parameters

I created a stored procedure which takes a view name and date as parameters and checks if there is record for that date in the view. However, I get the following error
'Operand type clash: date is incompatible with int'.
I am hoping that if the record exists that 1 will be returned else 0 will be returned and I can use that to make a decision in another stored procedure.
The code is listed below
CREATE PROCEDURE [dbo].[usr_RecordExist]
-- Add the parameters for the stored procedure here
#ViewName SYSNAME,
#TransDate Date
--<#Param2, sysname, #p2> <Datatype_For_Param2, , int> = <Default_Value_For_Param2, , 0>
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #DATEVARCHAR nvarchar(4000);
SET #DATEVARCHAR = CONVERT(NVARCHAR, #TransDate, 103);
-- Insert statements for procedure here
DECLARE #SQLCommand NVARCHAR(MAX) =
N'SELECT COUNT(SYMBOL) FROM ' + QUOTENAME(#ViewName) + 'WHERE TRANSDATE = ' + '' + #DATEVARCHAR + '';
EXECUTE [dbo].[sp_executesql]
#sqlCommand;
END
The expression + '' does nothing, use + '''' to add a single quote.
... + '''' + #DATEVARCHAR + '''';
You are using the right tools but in the wrong way, You should not concatenate parameters but pass them as parameters to the system stored procedure sp_executesql as shown below:
CREATE PROCEDURE [dbo].[usr_RecordExist]
#ViewName SYSNAME,
#TransDate Date
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLCommand NVARCHAR(MAX);
SET #SQLCommand = N'SELECT COUNT(SYMBOL) FROM ' + QUOTENAME(#ViewName)
+ N' WHERE TRANSDATE = #TransDate';
EXECUTE [dbo].[sp_executesql] #sqlCommand
,N'#TransDate Date'
,#TransDate
END
Edit
To get the count in an output parameter you would do the following:
CREATE PROCEDURE [dbo].[usr_RecordExist]
#ViewName SYSNAME,
#TransDate Date,
#Count INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLCommand NVARCHAR(MAX);
SET #SQLCommand = N'SELECT #Count = COUNT(SYMBOL) FROM ' + QUOTENAME(#ViewName)
+ N' WHERE TRANSDATE = #TransDate';
EXECUTE [dbo].[sp_executesql] #sqlCommand
,N'#TransDate Date, #Count INT OUTPUT'
,#TransDate
,#Count OUTPUT
END
Since you used QUOTENAME() for ViewName why not QUOTENAME(#DATEVARCHAR, '''') or QUOTENAME(#DATEVARCHAR, CHAR(39))
Cosmin got it. Although I also noticed you set #DATEVARCHAR to NVARCHAR(4000) even though convert(NVARCHAR without a length defaults to 30.

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

Search from Multiple columns in Where clause for SQL Server

We have a stored procedure that is used to allow users to search in a table with 20 million records and 40 columns wide. There are about 20 different columns they can search on (any combination) from and all those columns are in the WHERE clause.
Furthermore each columns is checked for Null and needs to be able to search with just part of the data.
Here is an example
(
#FirstName IS NULL
OR (RTRIM(UPPER(FirstName)) LIKE RTRIM(UPPER(#FirstName)) + '%')
)
AND (#LastName IS NULL)
What is a best way to rewrite this stored procedure? Should I break this stored procedure into multiple small stored procedures? If so how? I will need to allow user to search
When I look at the execution plan, regardless of what columns are passed, it always does the index scan
I had exactly this situation years ago, millions of rows and numerous filter parameters and the best method is to use dynamic sql. Construct a SQL statement based on the parameters that have values, then execute the SQL statement. (EXEC sp_executesql #sql)
The select clause of the sql statement is static but the from clause and the where clause is based on the parameters.
CREATE PROCEDURE dbo.DynamicSearch
#FirstName VARCHAR(20),
#LastName VARCHAR(20),
#CompanyName VARCHAR(50)
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX) = N''
DECLARE #Select NVARCHAR(MAX) = N'SELECT ColA, ColB, ColC, ColD '
DECLARE #From NVARCHAR(MAX) = N'From Person'
DECLARE #Where NVARCHAR(MAX) = N''
IF #FirstName IS NOT NULL
Begin
Set #Where = #Where + 'FirstName = ''' + #FirstName + ''''
End
IF #LastName IS NOT NULL
Begin
if len(#Where) > 0
Begin
Set #Where = #Where + ' AND '
End
Set #Where = #Where + 'LastName = ''' + #LastName + ''''
End
IF #CompanyName IS NOT NULL
Begin
if len(#Where) > 0
Begin
Set #Where = #Where + ' AND '
End
Set #From = #From + ' inner join Company on person.companyid = company.companyid '
Set #Where = #Where + 'company.CompanyName = ''' + #CompanyName + ''''
End
Set #SQL = #Select + #From + #Where
EXECUTE sp_executesql #sql
END
To go down the dynamic SQL route you would use something like:
CREATE PROCEDURE dbo.SearchSomeTable
#FirstName VARCHAR(20),
#LastName VARCHAR(20),
#AnotherCol INT
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX) = N'SELECT SomeColumn FROM SomeTable WHERE 1 = 1',
#ParamDefinition NVARCHAR(MAX) = N'#FirstName VARCHAR(20),
#LastName VARCHAR(20),
#AnotherCol INT';
IF #FirstName IS NOT NULL
#SQL = #SQL + ' AND FirstName = #FirstName';
IF #LastName IS NOT NULL
#SQL = #SQL + ' AND LastName = #LastName';
IF #AnotherCol IS NOT NULL
#SQL = #SQL + ' AND AnotherCol = #AnotherCol';
EXECUTE sp_executesql #sql, #ParamDefinition, #FirstName, #LastName, #AnotherCol;
END
Otherwise you will need to use the OPTION (RECOMPILE) query hint to force the query to recompile each time it is run to get the optimal plan for the particular parameters you have passed.
Another possibility, though the above is perhaps the most logical, is to create one or more "search" criteria tables into which you insert the users selections, and then perform LEFT JOINS against the search criteria. You would only be able to do this for cases in which there is an equivalency and preferably an small datatype such as int. For string comparisons, these kind of joins would be potentially dreadful as far as performance goes. The only problem with the above suggestion (dynamic-sql) is the possibility of plan cache bloat as each execution in which there is only one difference with an existing plan would cause a new plan to be generated.