Dynamic vs Static SQL with multiple conditions - sql

The "Where" clause of my SQL will vary depending on the values provided. I wrote 2 versions (Dynamic and static). I read that Dynamic is the way to go if we have multiple parameters with a variable where clause. Also, this is just a sample, my real SQL has many more conditions, but something along these lines. Which among the two is the right choice for this scenario?
Dynamic SQL:
DECLARE #SQL NVARCHAR(max)='SELECT ID,Name,sex,street,city,state,zip,phone FROM Test';
DECLARE #whereSql VARCHAR(2000) = 'WHERE city= #City';
DECLARE #OrderBy VARCHAR(2000) = 'order by name';
IF(#Name IS NOT NULL)
BEGIN
SET #Name = #Name + '%'
SET #whereSql = #whereSql + ' ' + 'AND name like #Name';
end
IF(#Gender IS NOT NULL)
BEGIN
SET #whereSql = #whereSql + ' ' + 'AND sex =' + '#Gender';
END
IF(#Address IS NOT NULL)
BEGIN
SET #Address = '%' + #Address + '%'
SET #whereSql = #whereSql + ' ' + 'AND street like ' + '#Address';
END
SET #SQL = #SQL +' ' + #whereSql + ' '+ #OrderBy;
EXEC sp_executesql #SQL, N'#Gender VARCHAR(10), #Name VARCHAR(200), #Address VARCHAR(250)', #Gender,#Name,#Address;
Static SQL:
SELECT ID,Name,sex,street,city,state,zip,phone FROM Test T1 WHERE
(#Name IS NULL OR (T1.Name LIKE +'%'+ #Name + '%'))
AND
(#Gender is null OR (T1.sex = #Gender))
AND
(#Address is null or (T1.street LIKE +'%'+ #Address + '%'))

Related

Multiple search filter using stored procedure

I already have a stored procedure for the search filter but it's complex and long, how do enhance the stored procedure code?
I have 3 search filters: group, key and label, these search filters are related to one another.
My stored procedure code:
IF (#group <> '' AND #key <> '' AND #label <> '')
BEGIN
SET #statement =
#statement + ' WHERE ([group] LIKE ''%' + #group + '%'' AND [key] LIKE ''%' + #key + '%'' AND [label] LIKE ''%' + #label + '%'')'
END
ELSE IF (#group <> '' AND #key <> '')
BEGIN
SET #statement =
#statement + ' WHERE ([group] LIKE ''%' + #group + '%'' AND [key] LIKE ''%' + #key + '%'')'
END
ELSE IF (#key <> '' AND #label <> '')
BEGIN
SET #statement =
#statement + ' WHERE ([key] LIKE ''%' + #key + '%'' AND [label] LIKE ''%' + #label + '%'')'
END
ELSE IF (#label <> '' AND #group <> '')
BEGIN
SET #statement =
#statement + ' WHERE ([label] LIKE ''%' + #label + '%'' AND [group] LIKE ''%' + #group + '%'')'
END
ELSE IF (#group <> '')
BEGIN
SET #statement
= #statement + ' WHERE [group] LIKE ''%' + #group + '%'''
END
ELSE IF (#key <> '')
BEGIN
SET #statement
= #statement + ' WHERE [key] LIKE ''%' + #key + '%'' '
END
ELSE IF (#label <> '')
BEGIN
SET #statement
= #statement + ' WHERE [label] LIKE ''%' + #label + '%'''
END
How do I modify the code to be simpler?
SET #statement =
#statement + ' WHERE
( (#group<>'' and [group] LIKE ''%' + #group + '%''') or (#group='' and 1=1))
( (#key <>'' and [key] LIKE ''%' + #key + '%''') or (#key ='' and 1=1))
( (#label <>'' and [label] LIKE ''%' + #label + '%''') or (#label ='' and 1=1))
Please try using above way using sql injection
I have used the following pattern in the past when creating stored procedures with input arguments that represent multiple optional search criteria.
The advantage of this technique are
No dynamic SQL
The query is clean
The WHERE clause has multiple ANDs (no ORs) allowing SQL to use any indexes you may have on the search columns
This is only an example but hopefully you get the idea and it's something you can build on.
-- Procedure Input Arguments
DECLARE
#IntExample INT = NULL
,#StringExample VARCHAR(50) = NULL;
-- Constants
DECLARE #MinInt32 INT = (-2147483648), #MaxInt32 INT = 2147483647;
-- Input Argument Validation
DECLARE #IdMin INT = #MinInt32, #IdMax INT = #MaxInt32;
IF (#IntExample IS NOT NULL)
BEGIN
SET #IdMin = #IntExample;
SET #IdMax = #IntExample;
END
DECLARE #DescFilter NVARCHAR(50) = '%';
IF (#StringExample IS NOT NULL)
BEGIN
SET #DescFilter = '%' + #StringExample + '%';
END
-- Procedure Query
SELECT *
FROM dbo.MyTable
WHERE
(
Id BETWEEN #IdMin AND #IdMax
AND [Description] LIKE #DescFilter
);
Assuming that you're only building dynamic SQL for the WHERE clause's sake.
This one will probably perform best due to short circuiting
SELECT *
FROM dbo.MyTable
WHERE
((#group = '') OR ([group] LIKE '%' + #group + '%'))
AND ((#key = '') OR ([key] LIKE '%' + #key + '%'))
AND ((#label = '') OR ([label] LIKE '%' + #label + '%'))
This will also work, but it will be heavier on resources and not as readable as the one above.
SELECT *
FROM dbo.MyTable
WHERE
([group] LIKE STUFF('%%', 2, 0, #group) )
AND ([key] LIKE STUFF('%%', 2, 0, #key) )
AND ([label] LIKE STUFF('%%', 2, 0, #label) )

how to create a dynamic update query with variables?

I am trying to create the below dynamic update query with some variables and for some reason, it's not working inside the stored procedure. Can someone suggest to me where I am doing wrong and what's the best practice by avoiding the SQL Injection as well?
DECLARE #SQL NVARCHAR(MAX)
DECLARE #COLUMN1 NVARCHAR(10)
DECLARE #COLUMN2 NVARCHAR(10)
DECLARE #TABLENAME NVARCHAR(10)
SET #SQL = 'UPDATE TL
SET '+ #COLUMN1 + '= AB.COLUMN1,'
+ #COLUMN2 + '= AB.COLUMN2
FROM' + #TABLENAME + ' TL
JOIN ABACUS AB
ON TL.REF = AB.REF
AND TL.SUBS = AB.SUBS
WHERE ' + #COLUMN1 + ' IS NULL
AND ' + #COLUMN2 +' IS NULL';
SET #COLUMN1 = (SELECT CONCAT('USER_ID', '8'))
SET #COLUMN2 = (SELECT CONCAT('USER_ID', '6'))
SET #TABLENAME = 'POLICYREF';
EXEC sys.sp_executesql #SQL, #TABLENAME, #COLUMN1, #COLUMN2;
SET #TABLENAME = 'USERREF';
EXEC sys.sp_executesql #SQL, #TABLENAME, #COLUMN1, #COLUMN2;
You need dynamic SQL, not parameters. You can't parameterize column names or table names. So something like:
DECLARE #SQL NVARCHAR(MAX)
DECLARE #COLUMN1 NVARCHAR(10) = 'USER_ID8'
DECLARE #COLUMN2 NVARCHAR(10) = 'USER_ID6'
DECLARE #TABLENAME NVARCHAR(10) = 'POLICYREF'
SET #SQL = 'UPDATE TL
SET '+ quotename(#COLUMN1) + '= AB.COLUMN1,'
+ quotename(#COLUMN2) + '= AB.COLUMN2
FROM ' + quotename(#TABLENAME) + ' TL
JOIN ABACUS AB
ON TL.REF = AB.REF
AND TL.SUBS = AB.SUBS
WHERE ' + quotename(#COLUMN1) + ' IS NULL
AND ' + quotename(#COLUMN2) +' IS NULL';
EXEC (#SQL)
SET #TABLENAME NVARCHAR(10) = 'USERREF'
SET #SQL = 'UPDATE TL
SET '+ quotename(#COLUMN1) + '= AB.COLUMN1,'
+ quotename(#COLUMN2) + '= AB.COLUMN2
FROM ' + quotename(#TABLENAME) + ' TL
JOIN ABACUS AB
ON TL.REF = AB.REF
AND TL.SUBS = AB.SUBS
WHERE ' + quotename(#COLUMN1) + ' IS NULL
AND ' + quotename(#COLUMN2) +' IS NULL';
EXEC (#SQL)
Not a huge fan of this but, given that, create a stored procedure OR re-arrange to execute each after updating the #SQL, here is the stored procedure example:
Note this is missing production level things like a transaction, TRY CATCH etc. and is only for an basic UNTESTED example
CREATE PROCEDURE dbo.MyFunQuery
#SQL NVARCHAR(MAX),
#COLUMN1 NVARCHAR(10),
#COLUMN2 NVARCHAR(10),
#TABLENAME NVARCHAR(10)
AS
BEGIN
SET #SQL = 'UPDATE TL
SET '+ #COLUMN1 + '= AB.COLUMN1,'
+ #COLUMN2 + '= AB.COLUMN2
FROM ' + #TABLENAME + ' AS TL
JOIN ABACUS AS AB
ON TL.REF = AB.REF
AND TL.SUBS = AB.SUBS
WHERE ' + #COLUMN1 + ' IS NULL
AND ' + #COLUMN2 + ' IS NULL;';
EXECUTE ( #SQL );
END
--Now to call it:
DECLARE #COLUMN1 NVARCHAR(10) = 'USER_ID8',
#COLUMN2 NVARCHAR(10) = 'USER_ID6';
EXECUTE dbo.MyFunQuery #COLUMN1, #COLUMN2, #TABLENAME='POLICYREF';
EXECUTE dbo.MyFunQuery #COLUMN1, #COLUMN2, #TABLENAME='USERREF';

SQL with AND statements, dynamically building up

Aim: I'm building a dynamic SQL string and want to make the search an AND function
Code type: SQL stored procedure within SQL Server Management Studio
Issue: If the first search is not required then I need to know this (I know because the default is '0' in this case. I feel I'm missing a sitter but don't seem to be able to stackoverflow/Google for the solution.
I set up #QueryString with a default of '' so the functionality will work.
What will fix this?:
I've thought about COALESCE and potential use of IF ELSE within the IF but I am hoping there is clean solution along the lines of
SET #QUERYSTRING = IF(#QUERYSTRING = '','', + + ' FIELD1 LIKE ''%' + LTRIM(RTRIM(#s1)) + '%' )
Current example (snippet):
ALTER PROCEDURE [dbo].[spGridSearchTest]
#s1 NVARCHAR(20),
#s2 VARCHAR(20)
AS
BEGIN
DECLARE #QUERY NVARCHAR(MAX) = ''
DECLARE #QUERYSTRING NVARCHAR(MAX) = ''
SET #QUERY = 'SELECT * FROM TblTable'
IF #s1 <> '1234xyz'
SET #QUERYSTRING = #QUERYSTRING + ' Field1 LIKE ''%' + LTRIM(RTRIM(#s1)) + '%'
IF #s2 <> '1234xyz'
SET #QUERYSTRING = #QUERYSTRING + ' Field2 LIKE ''%' + LTRIM(RTRIM#s2)) + '%'
IF LEN(LTRIM(RTRIM(#QUERYSTRING))) > 0
SET #QUERY = LTRIM(RTRIM(#QUERY)) + ' WHERE ' + LTRIM(RTRIM(#QUERYSTRING)) + ''''
EXECUTE(#QUERY)
END
If I understand better your issue:
Try this:
ALTER PROCEDURE [dbo].[spGridSearchTest]
#s1 NVARCHAR(20),
#s2 VARCHAR(20)
AS
BEGIN
DECLARE #QUERY NVARCHAR(MAX) = ''
DECLARE #QUERYSTRING NVARCHAR(MAX) = ''
DECLARE #conditionadded char(1) = 'N'
SET #QUERY = 'SELECT * FROM TblTable'
IF #s1 <> '1234xyz'
BEGIN
SET #QUERYSTRING = ' Field1 LIKE ''%' + LTRIM(RTRIM(#s1)) + '%'
SET #conditionadded = 'Y'
END
IF #s2 <> '1234xyz'
BEGIN
IF (#conditionadded = 'Y')
BEGIN
SET #QUERYSTRING = #QUERYSTRING + ' AND '
END
SET #QUERYSTRING = #QUERYSTRING + ' Field2 LIKE ''%' + LTRIM(RTRIM#s2)) + '%'
SET #conditionadded = 'Y'
END
IF (#conditionadded = 'Y')
BEGIN
SET #QUERY = LTRIM(RTRIM(#QUERY)) + ' WHERE ' + LTRIM(RTRIM(#QUERYSTRING)) + ''''
END
EXECUTE(#QUERY)
END
Do you really need a dynamic query? Why not use something like this:
select
Whatever
from MyTable
where
(#s1 = '1234xyz' or Field1 = #s1)
and
(#s2 = '1234xyz' or Field2 = #s2)
This avoids a security hole, and depending on your query patterns and data set, it might even be faster. And of course, it's pretty easy to read, and you don't have to deal with SQL in strings :)

Multiple conditional Where clause

I currently have a query that will pull a bunch of information from my database based on whatever where condition that I want to use.
declare #CaseNum char(7),
#ImportId char,
#FormatId char,
#SessionId char(5)
set #CaseNum = '' --I can place the value that I want to search by in here
set #ImportId = ''
set #FormatId = ''
set #SessionId = ''
--------------------
query in here
--------------------
where
gr.[CaseNum] = #CaseNum --currently I have to comment the ones I'm not using out
--im.[ImportId] = #ImportId
--fr.[FormatId] = #FormatId
--se.[SessionId] = #SessionId
I want to be able to take the comment portion out and simply display all rows if the parameter = ''
For example if I use set #CaseNum = '1234567' then it will search by that parameter and if I use #FormatId = '12' it will search by that one.
I have tried using the following and a few other attempts but I am getting nowhere fast.
where
gr.[CaseNum] = '%' + #CaseNum + '%'
and im.[ImportId] = '%' + #ImportId + '%'
and fr.[FormatId] = '%' + #FormatId + '%'
and se.[SessionId] = '%' + #SessionId + '%'
With help from the link that #Norman posted I figured it out. I wanted to post my solution for others to see.
declare #CaseNum varchar(MAX),
#ImportId varchar(MAX)
set #CaseNum = ''
set #ImportId = ''
----------------------------------------------------------------------------
If(#CaseNum = '') --Sets the parameter to NULL for COALESCE to work
Begin
Select #CaseNum = NULL
End
If(#ImportId = '') --Sets the parameter to NULL for COALESCE to work
Begin
Select #ImportId = NULL
End
--------------------
query in here
--------------------
where
gr.[CaseNum] = COALESCE(#CaseNum, gr.[CaseNum])
and im.ImportId = COALESCE(#ImportId, im.ImportId)
This solution allows the query to use just a single parameter or all at the same time.
You might want to look into building your query.
DECLARE #Number varchar(10)
DECLARE #Where varchar(max)
DECLARE #Query varchar(max)
SET #Query = 'SELECT * FROM TestTable'
SET #Where = ''
SET #Number = '3'
IF ISNULL(#Number, '') != ''
BEGIN
SET #Where = #Where + 'and testNumber = ' + #Number
END
IF LEN(#Where) > 0
BEGIN
SET #Where = SUBSTRING(#Where, 4, LEN(#Where))
END
if ISNULL(#Where, '') != ''
BEGIN
SET #Query = #Query + ' WHERE ' + #Where
END
EXEC(#Query)
Check out this gentleman's article for reference: https://social.msdn.microsoft.com/forums/sqlserver/en-US/1ec6ddd9-754b-4d78-8d3a-2b4da90e85dc/dynamically-building-where-clauses

How to from query escaping comma and other special character in SQL Server stored procedure

I have a function which returns the comma separate values, but when I use
COALESCE(#Jiraids + ', ', '')
I get an error.
ALTER FUNCTION [dbo].[getCommonJiraIds](
#selct varchar(100),
#whereClause varchar(100),
#id varchar(100),
#TEST_RESULT_INFO_ID varchar(20) )
RETURNS varchar(max)
AS
BEGIN
DECLARE #Jiraids VARCHAR(8000)
DECLARE #sql varchar(max)
SET #whereClause = 'dbo.SNSTestResults.JIRA_ID <> '' AND dbo.SNSTestResults.'+#whereClause+ ''+ QUOTENAME(#id,'''') + 'AND dbo.SNSTestResults.Test_result_info_ID_FK LIKE '+QUOTENAME(#TEST_RESULT_INFO_ID,'''')
SET #sql = 'Select #Jiraids = COALESCE(#Jiraids + '', '', '') + case when (#selct LIKE Jira_ID_Maped) then Jira_ID_Maped else Jira_ID end
FROM dbo.TestCaseList INNER JOIN dbo.SNSTestResults ON dbo.TestCaseList.TestCaseListID = dbo.SNSTestResults.TestCaseListID_FK'
set #sql = #sql + #WhereClause
EXEC #sql
RETURN ( select #Jiraids);
END
When I execute the above function I got this error:
Msg 203, Level 16, State 2, Procedure GetDailyCrJiraTable, Line 159
The name 'Select #Jiraids =COALESCE(#Jiraids + ', ', ') + case when (#selct LIKE Jira_ID_Maped) then Jira_ID_Maped else Jira_ID end FROM dbo.TestCaseList INNER JOIN dbo.SNSTestResults ON dbo.TestCaseList.TestCaseListID = dbo.SNSTestResults.TestCaseListID_FKdbo.SNSTestResults.JIRA_ID <> ' AND dbo.SNSTestResults.Jira_ID_Maped'UIBUG-4533'AND dbo.SNSTestResu' is not a valid identifier.
Somebody please help me to fix this issue.
SET #whereClause = 'dbo.SNSTestResults.JIRA_ID <> '''' AND dbo.SNSTestResults.'+#whereClause+ ''+ QUOTENAME(#id,'''') + 'AND dbo.SNSTestResults.Test_result_info_ID_FK LIKE '+QUOTENAME(#TEST_RESULT_INFO_ID,'''')
you need more ' after dbo.SNSTestResults.JIRA_ID <>
You have to add more ' to your COALESCE function as below
Select #Jiraids = COALESCE(#Jiraids + '', '', '''')
and as I said in comment you have to change this dbo.SNSTestResults.JIRA_ID <> ''
to dbo.SNSTestResults.JIRA_ID <> '''' AND
ALTER FUNCTION [dbo].[getCommonJiraIds](
#selct varchar(100),
#whereClause varchar(100),
#id varchar(100),
#TEST_RESULT_INFO_ID varchar(20) )
-- your four inputs
RETURNS varchar(max)
AS
BEGIN
DECLARE #Jiraids VARCHAR(8000)
DECLARE #sql varchar(max)
SET #whereClause = 'dbo.SNSTestResults.JIRA_ID <> ''
AND dbo.SNSTestResults.'+#whereClause+ ''+ QUOTENAME(#id,'''')
+ 'AND dbo.SNSTestResults.Test_result_info_ID_FK LIKE '+QUOTENAME(#TEST_RESULT_INFO_ID,'''')
you do not know it yet, but your Query does not actually have a WHERE clause written in.
I hope you are not actually expecting the programmer to write extra
code in your function everytime it is used.
SET #sql = 'Select #Jiraids = COALESCE(#Jiraids + '', '', '') + case when (#selct LIKE Jira_ID_Maped) then Jira_ID_Maped else Jira_ID end
FROM dbo.TestCaseList INNER JOIN dbo.SNSTestResults ON dbo.TestCaseList.TestCaseListID = dbo.SNSTestResults.TestCaseListID_FK
Your #selct has not been correctly identified. SQL SERVER cannot know
at compile time that it is a #variable.
Also, are you sure your function will only return one row? In some cases, being explicit about the expected occurrence (should someone actually use a '%' in their PREDICATE) can prevent a future breakdown in the code.
set #sql = #sql + #WhereClause
EXEC #sql
Thankfully, SO exists to help people out. :D
A correct version could be like the following:
ALTER FUNCTION [dbo].[getCommonJiraIds](
#selct varchar(100),
#whereClause varchar(100),
#id varchar(100),
#TEST_RESULT_INFO_ID varchar(20) )
-- your four inputs
RETURNS varchar(max)
AS
BEGIN
DECLARE #Jiraids VARCHAR(8000)
DECLARE #sql varchar(max)
SET #sql = '
Select #Jiraids = TOP 1 COALESCE(#Jiraids + '', '', '') + CASE WHEN (' + #selct + ' = Jira_ID_Maped)
THEN Jira_ID_Maped
ELSE Jira_ID END
FROM dbo.TestCaseList
INNER JOIN dbo.SNSTestResults ON dbo.TestCaseList.TestCaseListID = dbo.SNSTestResults.TestCaseListID_FK'
SET #whereClause = 'WHERE dbo.SNSTestResults.JIRA_ID <> ''
AND dbo.SNSTestResults.'+#whereClause+ ''+ QUOTENAME(#id,'''')
+ 'AND dbo.SNSTestResults.Test_result_info_ID_FK LIKE '+QUOTENAME(#TEST_RESULT_INFO_ID, '')
set #sql = #sql + #WhereClause
EXEC #sql
RETURN ( select #Jiraids);
END