How do I include optional parameters using IF-THEN-ELSE logic in a SQL query? - sql

Here is the create statement for a stored procedure:
Create Procedure SearchCreatedAssignments
(#LessonName Varchar(50), #DateFrom date, #DateTo Date, #LocationCode Varchar(10))
As
BEGIN
Basically, I want to write a query that searches the database based on the values of the parameters. For example:
Select *
from dbo.test
where (LessonName = #LessonName)
AND (StartDate = #DateFrom)
AND (EndDate = #DateTo)
AND (LocationCode = #LocationCode)
Fairly simple, right? However, if any of these parameters are null (or contain an empty string), I would like to omit them from the search, and search by only the parameters that are not null. I was thinking something like this:
--if #LocationCode is null OR #LocationCode = '' -> omit #LocationCode from the search
This is obviously pseudo code. How can I do this? Forgive me if this is a simple task; I am new to SQL.

Consider the following. If a parameter is NULL or empty, the default value will be the field in question
Select *
from dbo.test
where LessonName = IsNull(NullIf(#LessonName,''),LessonName)
AND StartDate = IsNull(NullIf(#DateFrom,''),StartDate)
AND EndDate = IsNull(NullIf(#DateTo,''),EndDate)
AND LocationCode = IsNull(NullIf(#LocationCode,''),LocationCode)

You can either write a dynamic SQL statement and execute it using sp_ExecuteSQL in your procedure, or you can get tricky with the SQL like:
Select *
from dbo.test
where (LessonName = #LessonName)
AND (StartDate = #DateFrom)
AND (EndDate = #DateTo)
AND (LocationCode = #LocationCode or #LocationCode IS NULL or #LocationCode = '')

You can use the COALESCE function to do so in this way:
where LessonName = coalesce(#LessonName, LessonName)
AND StartDate = coalesce(#DateFrom, StartDate)
AND EndDate = coalesce(#DateTo, EndDate)
AND LocationCode = coaleasce(#LocationCode, LocationCode)
Although I'm not sure about the empty strings. It will work for null values, in other databases coalesce also handle the empty strings. If it do not work you can use case in the same manner:
LessonName = case when #LessonName is not null and #LessonName != ''
then #LessonName else LessonName end
And just use the same logic for the other parameters.

INHO on this case a good way is using a dynamic query.
DECLARE #cmd VARCHAR(MAX);
SET #CMD = 'SELECT * FROM dbo.Text WHERE #Param1 = 'x''; --at least on parameter
IF #PARAM2 IS NOT NULL
BEGIN
SET #CMD = #CMD + ' AND Param2 = #Param2'
END
IF #PARAM3 IS NOT NULL
BEGIN
SET #CMD = #CMD + ' AND Param3 = #Param3'
END
EXECUTE (#CMD);

Related

Executing select statement as variable from TVF

I have to get a list of results from a Table value function from a variable. I have done something like this:
DECLARE #Date char(8) = '20200508'
DECLARE #Type varchar(100) = 'Inbound'
DECLARE #Offset INT = 3600
DECLARE #EmployeeID INT = null
DECLARE #TypeFunc as varchar(max)
SET #TypeFunc= N'select EmpID, Callcount from dbo.fn_' + #Type + '('''+ #Date +''','+ CAST(#Offset as Varchar(100))+','+ CAST(#EmployeeID as varchar(100))+')';
EXEC (#TypeFunc)
I expect to see a list of results as if I'm doing a normal select query, however, it is just coming back with 'Commands completed successfully.' in the results grid, which doesn't seem like its doing it correctly.
The query it should run should look like
Select EmpID, Callcount From dbo.fn_Inbound('20200508', 3600, null)
Anything I'm missing here?
I found 2 mistakes in your Query:
1.) Use CONCAT instead of + because if any of your concatenating string is null it makes the whole Concatenation as NULL (For your case EmpID is null it will makes the Whole Query as null by using +)
2.)ISNULL(CAST(#EmployeeID as varchar(100)),'NULL') Use ISNULL fn to pass as null for that Parameter in your function
SET #TypeFunc= CONCAT(N'select EmpID, Callcount from dbo.fn_' , #Type , '(''', #Date
,''',', CAST(#Offset as Varchar(100)),',',ISNULL(CAST(#EmployeeID as
varchar(100)),'NULL'),')');

Using different set of WHERE clauses in stored procedure depending on Parameter value

I have 2 stored procedures which return the same columns that I am trying to merge into a single procedure. They both have a different set of parameters and both have different WHERE clauses, but they use the same tables and select the exact same rows.
WHERE clause 1: (uses #UIOID, and #Level)
WHERE ( #UIOID = CASE WHEN #Level = 'Single' THEN C.C_UIOID_PK
WHEN #Level = 'Children' THEN CLC.UIOL_P
WHEN #Level = 'Parent' THEN CLP.UIOL_C
END
OR ( #UIOID = '0'
AND #Level = 'All'
)
)
Where clause 2: (Uses #TeamCode, #Year, #IncludeQCodes)
WHERE C.C_IsChild = 0
AND C.C_MOA <> 'ADD'
AND #TeamCode = C.C_OffOrg
AND C.C_Active = 'Y'
AND ( #Year BETWEEN dbo.f_GetAcYearByDate(C.C_StartDate) AND dbo.f_GetAcYearByDate(C.C_EndDate)
OR #Year = 0 )
AND ( C.C_InstCode NOT LIKE 'Q%'
OR #IncludeQCodes = 1 )
Ideally I want to add a new parameter which basically tells it which of the two WHERE clauses to run, but I can't seem to recreate that with CASE statement because as far as I can tell, they only work for a single WHERE clause, not a whole set of different clauses
I want to do this without having to repeat the select statement again and putting the whole thing in IF statements, and i don't want to put the query into a string either. I just want one select statement ideally.
The problem with using temp tables is the query itself takes a while to run without any parameters and is used in a live website, so I don't want it to have to put all records in a temp table and then filter it.
The problem with using a CTE is you can't follow it with an IF statement, so that wouldn't work either.
Here is the sort of logic I am trying to achieve:
SELECT A
B
C
FROM X
IF #WhichOption = 1 THEN
WHERE ( #UIOID = CASE WHEN #Level = 'Single' THEN C.C_UIOID_PK
WHEN #Level = 'Children' THEN CLC.UIOL_P
WHEN #Level = 'Parent' THEN CLP.UIOL_C
END
OR ( #UIOID = '0'
AND #Level = 'All'
)
)
ELSE IF #WhichOption = 2 THEN
WHERE C.C_IsChild = 0
AND C.C_MOA <> 'ADD'
AND #TeamCode = C.C_OffOrg
AND C.C_Active = 'Y'
AND ( #Year BETWEEN dbo.f_GetAcYearByDate(C.C_StartDate) AND dbo.f_GetAcYearByDate(C.C_EndDate)
OR #Year = 0 )
AND ( C.C_InstCode NOT LIKE 'Q%'
OR #IncludeQCodes = 1 )
Save the following process in a procedure. You can also directly insert into a physical table.
declare #varTable Table (columns exactly as Procedures return)
if(condition is met)
begin
insert into #varTable
exec proc1
end
else
begin
insert into #varTable
exec proc2
end
Add the parameter that you said that it would indicate what filter apply :
select XXXXX
from XXXXX
where (#Mode = 1 and ( filter 1 ))
or
(#Mode = 2 and ( filter 2 ))
option(recompile)
If the #Mode parameter is 1 then it will evaluate the filter 1, otherwise it will evaluate the filter 2.
Add an option(recompile) at the end of the statement, so the SQL engine will replace the variables with their values, eliminate the filter that won't be evaluated, and generate an execution plant for just the filter that you want to apply.
PS: Please notice that although these catchall queries are very easy to code and maintain, and generate a perfectly functional and optimal execution, they are not advised for high-demand applications. The option(recompile) forces the engine to recompile and generate a new execution plan at every execution and that would have a noticeable effect on performance if your query needs to be executed hundreds of times per minute. But for the occasional use it's perfectly fine.
Try to use dynamic SQL:
DECLARE #sql NVARCHAR(max), #where NVARCHAR(max), #WhichOption INT = 1;
SET #sql = 'SELECT A
B
C
FROM X';
IF #WhichOption = 1
SET #where = 'WHERE ( #UIOID = CASE WHEN #Level = ''Single'' THEN C.C_UIOID_PK
WHEN #Level = ''Children'' THEN CLC.UIOL_P
WHEN #Level = ''Parent'' THEN CLP.UIOL_C
END
OR ( #UIOID = ''0''
AND #Level = ''All''
)
)';
ELSE IF #WhichOption = 2
SET #where = ' WHERE C.C_IsChild = 0
AND C.C_MOA <> ''ADD''
AND #TeamCode = C.C_OffOrg
AND C.C_Active = ''Y''
AND ( #Year BETWEEN dbo.f_GetAcYearByDate(C.C_StartDate)
AND dbo.f_GetAcYearByDate(C.C_EndDate)
OR #Year = 0 )
AND ( C.C_InstCode NOT LIKE ''Q%''
OR #IncludeQCodes = 1 ) ';
SET #sql = CONCAT(#sql,' ', #where)
PRINT #sql
EXECUTE sp_executesql #sql

Using SQL Server CASE statement in WHERE

I want to select records from a table in a stored procedure. Given parameters can be empty or a string including some keys separated by comma (1, 2, etc)
I want to manage that when a parameter is an empty string, "WHERE" ignore searching.
I'm using this code:
where (CASE when #PatientID <> 0 then ( dental.ID_Sick in (1,2)) else (1=1) end)
Something like that is working in W3School. I mean:
SELECT * FROM Customers
WHERE (case when 1=1 then (Country IN ('Germany', 'France', 'UK')) else 1=1 end);
What is the problem in my query that does not work? SQLServerManagementStudio is giving error on "IN" statement.
Solution:
The best way to handle such optional parameters is to use dynamic SQL and built the query on the fly. Something like....
CREATE PROCEDURE myProc
#Param1 VARCHAR(100) = NULL
,#Param2 VARCHAR(100) = NULL
,#Param3 VARCHAR(100) = NULL
,#ListParam VARCHAR(100) = NULL
--, etc etc...
AS
BEGIN
SET NOCOUNT ON;
Declare #Sql NVARCHAR(MAX);
SET #Sql = N' SELECT *
FROM TableName
WHERE 1 = 1 '
-- add in where clause only if a value was passed to parameter
+ CASE WHEN #Param1 IS NOT NULL THEN
N' AND SomeColumn = #Param1 ' ELSE N'' END
-- add in where clause a different variable
-- only if a value was passed to different parameter
+ CASE WHEN #Param2 IS NOT NULL THEN
N' AND SomeOtherColumn = #Param3 ' ELSE N'' END
-- List Parameter used with IN clause if a value is passed
+ CASE WHEN #ListParam IS NOT NULL THEN
N' AND SomeOtherColumn IN (
SELECT Split.a.value(''.'', ''VARCHAR(100)'') IDs
FROM (
SELECT Cast (''<X>''
+ Replace(#ListParam, '','', ''</X><X>'')
+ ''</X>'' AS XML) AS Data
) AS t CROSS APPLY Data.nodes (''/X'') AS Split(a) '
ELSE N'' END
Exec sp_executesql #sql
, N' #Param1 VARCHAR(100), #Param2 VARCHAR(100) ,#Param3 VARCHAR(100) ,#ListParam VARCHAR(100)'
, #Param1
, #Param2
,#Param3
, #ListParam
END
Problem with Other approach
There is a major issue with this other approach, you write your where clause something like...
WHERE ( ColumnName = #Parameter OR #Parameter IS NULL)
The Two major issues with this approach
1) you cannot force SQL Server to check evaluate an expression first like if #Parameter IS NULL, Sql Server might decide to evaluate first the expression ColumnName = #Parameterso you will have where clause being evaluated even if the variable value is null.
2) SQL Server does not do Short-Circuiting (Like C#), even if it decides to check the #Parameter IS NULL expression first and even if it evaluates to true, SQL Server still may go ahead and evaluating other expression in OR clause.
Therefore stick to Dynamic Sql for queries like this. and happy days.
SQL Server does not have a Bool datatype, so you can't assign or return the result of a comparison as a Bool as you would in other languages. A comparison can only be used with IF-statements or WHERE-clauses, or in the WHEN-part of a CASE...WHEN but not anywhere else.
Your specific example would become this:
SELECT * FROM Customers
WHERE 1=1 OR Country IN ('Germany', 'France', 'UK')
It would be better readable to rewrite your statement as follows:
WHERE #PatientID = 0
OR dental.ID_Sick in (1,2)
Referring to your actual question, I'd advise to read the linked question as provided by B House.
May be this straight way will work for you
IF (#PatientID <> 0)
BEGIN
SELECT * FROM Customers
WHERE Country IN ('Germany', 'France', 'UK')
END
try this:
WHERE 1=(CASE WHEN #PatientID <>0 AND dental.ID_Sick in (1,2) THEN 1
WHEN #PatientID =0 THEN 1
ELSE 0
END)

How to pass dynamic View name in SQL stored procedure without using a dynamic query?

Here is my stored procedure below. I am concatenating #CultureCode paramter along with view name which is [_0002HR_EmployeeNames_en-US_View]. The part en-US will be passed through a parameter named as #CultureCode. Is there any way to do so because i have requirement not to use dynamic query. Thank you.
CREATE PROCEDURE [dbo].[_001HR_Report_Loans] (#Parameters VARCHAR(max))
AS
DECLARE #ReportOption VARCHAR(5) SET #ReportOption = [dbo].DB_Split(#Parameters, 1)
DECLARE #CultureCode VARCHAR(10) SET #CultureCode = [dbo].DB_Split(#Parameters, 2)
DECLARE #ShowItems VARCHAR(5) SET #ShowItems = [dbo].DB_Split(#Parameters, 3)
DECLARE #StartDate NVARCHAR(8) SET #StartDate = [dbo].DB_Split(#Parameters, 4)
DECLARE #EndDate NVARCHAR(8) SET #EndDate = [dbo].DB_Split(#Parameters, 5)
DECLARE #EmployeeCode NVARCHAR(30) SET #EmployeeCode = [dbo].DB_Split(#Parameters, 6)
DECLARE #BranchCode NVARCHAR(30) SET #BranchCode = [dbo].DB_Split(#Parameters, 7)
--IF #StartDate = ''
-- SET #StartDate = NULL
SELECT HR.*, EN.[Name] AS EmployeeName
FROM [0002HR_EmployeeLoans] HR
LEFT JOIN [_0002HR_EmployeeNames_ + '#CultureCode' +_View] EN ON HR.EmployeeCode = EN.EmployeeCode
LEFT JOIN [_0002HR_EmployeePackagesView] EP ON EP.EmployeeCode = HR.EmployeeCode
WHERE
(HR.EmployeeCode = #EmployeeCode OR #EmployeeCode IS NULL)
AND
(EP.BranchCode = #BranchCode OR #BranchCode IS NULL)
AND
(HR.Date BETWEEN #StartDate AND #EndDate OR #StartDate IS NULL AND #EndDate IS NULL)
AND
(HR.Date >= #StartDate OR #StartDate IS NULL)
AND
(HR.Date <= #EndDate OR #EndDate IS NULL)
This is not possible in T-SQL so far. Things like TOP clauses, table or column names can not be parameterized.
One way would be to create a union over all possible tables/views, add a new column that matches the table/view name and filter that.
Like this:
SELECT * FROM
(
SELECT 'Table1' as TableName, t1.* FROM Table1 t1 WHERE ...
UNION ALL
SELECT 'Table2' as TableName, t2.* FROM Table2 t2 WHERE ...
) tmp
WHERE TableName = #tableName
Another (and possibly the most "clean" way) would be to only have one single table and make the culture a column in that table, so you only need to pass the correct culture string to filter over that column.
So you have a view per language. They are getting the data from the tables for their particular language. But now you want to write a procedure that is not language-specific.
The solution seems simple: Don't use the language views, but access the tables directly instead. (Or build an all-languages view which you query with where language = #language.)

SQL IF else in where statement

ALTER PROCEDURE [dbo].[sp_CourseEvalRpt_2]
#coursetitle varchar(100) = null,
#datestart varchar(8),
#dateend varchar(8)
AS
select distinct
t_empname, t_leader, T_Dept, t_submit
from
tbltrain
where
t_category like 'course%'
and
if (#coursetitle <> '') t_course = #coursetitle end
How can I eliminate the SQL where criteria when parameter name #coursetitle is empty? I don't want to add the t_course condition when #coursetitle is empty, so system will display all data rather than set the t_course=''.
Try changing your sql to this:
select distinct t_empname,
t_leader,
T_Dept,
t_submit
from tbltrain
where t_category like 'course%'
and ( (#coursetitle<>''and t_course=#coursetitle)
or #coursetitle='')
You can simplify the where clause to:
where t_category like 'course%' and
(#coursetitle = '' or t_course = #coursetitle)
If you used NULL instead of an empty string for #coursetitle, you could phrase it as:
where t_category like 'course%' and
t_course = coalesce(#coursetitle, t_course)
How about using dynamic SQL ? so that you can perform a clear IF ELSE condition.
Just add SQLQuery variable for the SQL query and use dynamic SQL
ALTER PROCEDURE [dbo].[sp_CourseEvalRpt_2]
#coursetitle varchar(100)=null,
#datestart varchar(8),
#dateend varchar(8),
#SQLQuery varchar(max) -->> Additional variable for dynamic SQL
AS
SET #SQLQuery = 'select distinct t_empname,t_leader,T_Dept,t_submit
from tbltrain where t_category like course%'
IF ISNULL(#coursetitle,'') <> ''
BEGIN
SET #SQLQuery = #SQLQuery + 'AND t_course = ' + #coursetitle
END
EXEC (#SQLQuery)