Adding to WHERE clause conditions using CASE - sql

I have a stored procedure that accepts an optional #ID param. When the param is passed in, I want the WHERE statement to include something like id = #ID, otherwise, when #ID is null, I don't want it to be filtered.
For example:
#ID BIGINT = NULL
SELECT * from myTable
WHERE
CASE
WHEN #ID IS NOT NULL THEN mytable.id = #ID
END
I am running this in SQL server 2016 and it says bad syntax near mytable.id = #ID. Can CASE be used in this way or should I try a different SQL method for this?
The only other option I considered to accomplish this was by using IF conditions in my stored procedure, but that didn't seem possible either based on my searches.

CASE is an expression, not a statement. It is not used to control flow like this and it will not work.
Your logic would need to be something like this.
Where mytable.id = ISNULL(#ID, mytable.id)
I should caution you that this pattern can lead to some poor performance. For a more detailed explanation and some other options you should check out this article. http://www.sqlinthewild.co.za/index.php/2009/03/19/catch-all-queries/

A bad-performance approach would be:
WHERE ISNULL(#ID,mytable.id) = mytable.id
A better-performance approach would be:
if(#ID IS NULL)
select * from ... without the WHERE condition
else
do your query with the condition mytable.id = #ID
Or build the query dynamically in the stored proc and execute it through sp_executesql passing parameters
Note: If the table is small enough, stick to simplicity and use the first option.

Related

Correct usage of WHERE and AND in procedure with optional parameters

Pre-question info:
I'm writing a stored-procedure that would take some parameters and depending on those parameters(if they are filled - because they don't have to be) I'm adding few where clauses. The thing is I don't know if I'm gonna even use the where clause from start because I don't know if any of my params is going to be non-empty/not-null.
The inside of procedure looks cca like:
BEGIN
DECLARE #strMySelect varchar(max)
SET #strMySelect ='SELECT myparams FROM mytable'
// add some WHERE statement(*)
IF(ISNULL(#myParamDate1,'')<>'')BEGIN
SET #strMySelect =#strMySelect +'
AND param1 >='''+CAST(#myParamDate1 as varchar(30))+''''
END
IF(ISNULL(#myParamDate2,'')<>'')BEGIN
SET #strMySelect =#strMySelect +'
AND param1 <='''+CAST(#myParamDate2 as varchar(30))+''''
END
//... bit more of these "AND"s
EXECUTE(#strExec)
QUESTION:
Is it ok(correct way of doing this) to put in my query some WHERE statement that I know that will be always true so I can use in my parameter cases AND always? OR do I have to check for each param if it's first one that is filled or is there an easy way of checking in SQL that at least one of my parameters isn't NULL/empty?
I handle optional parameters like this:
where (
(#optionalParameter is not null and someField = #optionalParameter )
or
#optionalParameter is null
)
etc
I find it simpler.
Your extra where clause is not a problem from a performance point-of-view, since the query optimizer will (likely) remove the 1 = 1 condition anyway.
However, I would recommend a solution along the lines of what Dan Bracuk suggested for two reasons:
It is easier to read, write and debug.
You avoid the possibility of SQL injection attacks.
There are cases where you have to custom-build your query-string (e.g. when given a table name as parameter), but I would avoid it whenever possible.
You don't need to use EXEC function to check for parameters. A good practice is using case to check for parameter value for example
CREATE PROCEDURE MyProc
#Param1 int = 0
AS
BEGIN
SELECT * FROM MyTable WHERE CASE #param1 WHEN 0 THEN #param1 ELSE MyField END = #Param1
END
GO
In case that #param1 has no value (default 0) then you have #param1=#param1 which gives always true, in case you have #param with value then condition is MyField=#param1.

SQL Server Stored Procedure WHERE Clause IF CASE No Value

I have a stored procedure with default values, which I set to NULL. In the WHERE clause, for each value specified, rather than NULL or not given (where it would also be NULL), I would like the column to equal this value, otherwise, I would like to search without this column being in the WHERE clause. The basic structure looks something like this:
// Set up the stored procedure
USE Table;
GO
CREATE PROCEDURE dbo.SearchTable
// Specify value(s)
#Name varchar(50) = NULL
AS
// Set up the query
IF(#Name IS NOT NULL
SELECT * FROM Table WHERE Name=#Name;
ELSE
SELECT * FROM Table
BEGIN
END
I have more than 1 parameter, and so, with this logic, I would need 2 IF's for every parameter. As you can imagine, this gets boring, time-consuming, and error-prone fast.
I would like to move this logic into the query, preferably into the WHERE clause, but any way I have found will cause errors (besides exceptions, which would require just as many IF's). SQL Server doesn't like IF's in the WHERE clause as far as I know, and with CASE I would have to specify the column, which I do not want to do.
What should I do?
Edit:
I have SQL Server version 2012, so please concentrate on this or any recent versions in your answer.
If you don't care about performance, you can do:
SELECT *
FROM Table
WHERE #Name is null or Name = #Name;
Often, having an or condition gets in the way of efficient use of indexes. Perhaps this isn't a problem in your case, though.
You could do something like this. The downside to this is that indexes may not be used properly and thus the performance may not be great.
SELECT * FROM Table
WHERE (#Name Is Null Or Name = #Name)
And (#Col2 Is Null Or Col2 = #Col2)
And (#Col3 Is Null Or Col3 = #Col3)
Each column condition is "anded". Or is used to apply that column condition only if #var is not null. So for example, if this is called with just #Name populated, it is equivalent to Where Name = #Name. If both #Name and #Col2 are populated, it is equivalent to Where Name = #Name And Col2 = #Col2.

T-SQL: WHERE Clause - How to adjust based on input variable

Using SQL Server 2008. I have an input param in my stored proc called '#State'. The param can basically be '--All--' or can contain the state to filter.
So, if it is '--All--' I don't want to incorporate the #State into the where clause. Otherwise I'd like it to filter based on the provided #State. So basically it could result in this....
SELECT * FROM MyTable
WHERE Type='AAA' AND Status=#Status
or, if they pass '--All--'
SELECT * FROM MyTable
WHERE Type='AAA'
How can I do this in a stored proc?
SELECT
*
FROM
MyTable
WHERE
Type='AAA'
AND Status = CASE #Status WHEN '--All--' THEN Status ELSE #Status END
I thought you made a typo. It should be #State, not #Status. This simple query might not be what you are looking for since you want to two sql statements in your requirement.
SELECT * FROM MyTable
WHERE Type='AAA' AND (#State='--All--' or State=#State)
Or...
you could make it even simpler than that:
SELECT * FROM MyTable
WHERE Type='AAA' AND #State in ('--All--', State)
No need to do it in a stored procedure, unless absolutely necessary (or if business/coding practice requires you to do so).

How would you build one Select stored procedure to handle any request from a table?

I want to build a single select stored procedure for SQL 2005 that is universal for any select query on that table.
**Columns**
LocationServiceID
LocationID
LocationServiceTypeID
ServiceName
ServiceCode
FlagActive
For this table I may need to select by LocationServiceID, or LocationID, or LocationServiceTypeID or ServiceName or a combination of the above.
I'd rather not have a separate stored procedure for each of them.
I assume the best way to do it would be to build the 'WHERE' statement on NOT NULL. Something like
SELECT * FROM LocationServiceType WHERE
IF #LocationID IS NOT NULL (LocationID = #LocationID)
IF #LocationServiceID IS NOT NULL (LocationServiceID = #LocationServiceID)
IF #LocationServiceTypeID IS NOT NULL (LocationServiceTypeID = #LocationServiceTypeID)
IF #ServiceName IS NOT NULL (ServiceName = #ServiceName)
IF #ServiceCode IS NOT NULL (ServiceCode = #ServiceCode)
IF #FlagActive IS NOT NULL (FlagActive = #FlagActive)
Does that make sense?
here is the most extensive article I've ever seen on the subject:
Dynamic Search Conditions in T-SQL by Erland Sommarskog
here is an outline of the article:
Introduction
The Case Study: Searching Orders
The Northgale Database
Dynamic SQL
Introduction
Using sp_executesql
Using the CLR
Using EXEC()
When Caching Is Not Really What You Want
Static SQL
Introduction
x = #x OR #x IS NULL
Using IF statements
Umachandar's Bag of Tricks
Using Temp Tables
x = #x AND #x IS NOT NULL
Handling Complex Conditions
Hybrid Solutions – Using both Static and Dynamic SQL
Using Views
Using Inline Table Functions
Conclusion
Feedback and Acknowledgements
Revision History
First of all, your code will not work. It should look like this:
SELECT * FROM LocationServiceType WHERE
(#LocationID IS NULL OR (LocationID = #LocationID)
... -- all other fields here
This is totally valid and known as 'all-in-one query'. But from a performance point of view this is not a perfect solution as soon as you don't allow SQL Server to select optimal plan. You can see more details here.
Bottom line: if your top priority is 'single SP', then use this approach. In case you care about the performance, look for a different solution.
SELECT *
FROM LocationServiceType
WHERE LocationServiceID = ISNULL(#LocationServiceID,LocationServiceID)
AND LocationID = ISNULL(#LocationID,LocationID)
AND LocationServiceTypeID = ISNULL(#LocationServiceTypeID,LocationServiceTypeID)
AND ServiceName = ISNULL(#ServiceName,ServiceName)
AND ServiceCode = ISNULL(#ServiceCode,ServiceCode)
AND FlagActive = ISNULL(#FlagActive,FlagActive)
If a null value is sent in it will cancel out that line of the where clause, otherwise it will return rows that match the value sent in.
What I've always done is is set the incoming parameters to null if should be ignored in query
then check variable for null first, so if variable is null condition short circuits and filter is not applied. If variable has value then 'or' causes filter to be used. Has worked for me so far.
SET #LocationID = NULLIF(#LocationID, 0)
SET #LocationServiceID = NULLIF(#LocationServiceID, 0)
SET #LocationServiceTypeID = NULLIF(#LocationServiceTypeID, 0)
SELECT * FROM LocationServiceType WHERE
(#LocationID IS NULL OR LocationID = #LocationID)
AND (#LocationServiceID IS NULL OR LocationServiceID = #LocationServiceID)
AND (#LocationServiceTypeID IS NULL OR #LocationServiceTypeID = #LocationServiceTypeID)
etc...

Best way to filter queries by parameter?

I have been using this method to filter my queries:
Create PROCEDURE [dbo].[pGetTask]
#showCompletedTasks bit = 1
,#showInProgressTasks bit = 1
,#taskID int = null
,#projectID int = null
,#applicationID int = null
,#clientID int = null
... Snip ...
where
a.clientID = isnull(#clientID, a.clientID)
and a.applicationID = isnull(#applicationID, a.applicationID)
and p.projectID = isnull(#projectID, p.projectID)
and t.taskID = isnull(#taskID, t.taskID)
and curr.complete = case #showCompletedTasks when 0 then 0 else curr.complete end
and curr.complete = case #showInProgressTasks when 0 then 1 else curr.complete end
This actually slows my queries by 2 seconds on a 664 row result set. The SQL tuning advisor isn't much help, so I figure this is not the right way to do this. Is there a right way, besides a ton of if statements?
Assuming you have properly indexed the table that the select is on, and these fields are part of the index, my guess is that it would be the calls to isnull. I would change them to this:
(#clientID is null or a.clientID = #clientId) and ...
As for the case statements, indexes on bit fields are pointless, so there's not much to do there.
Check your indexes & statistics. That seems a little slow. The other option would be to do a dynamic query essentially build a string representing your sql and execute it using sp_ExecuteSql (or Exec statement)
Edit
You could try and combine your two cases but I doubt it will have effect on performance of the query. It would look better though...
Although I'm not sure your query is right (Which is hard to say without more info) but shouldn't there be an or clause between the cases your trying to provide two states to return and by having separate params I assume I can ask for Only Complete, Only Not Complete or both...in this case you need an Or
Your best bet is to use this stored procedure to call a series of more specific procedures. You have two issues:
The use of the case statement
causes a table scan, which
(obviously) ignores any indexes you
might have
Even if you break the
statement out into several that are
called conditionally, you'll still
end up with a compiled execution
plan that is specific to the first
call to this procedure.
If you create specific procedures, like pGetTask_Completed and pGetTask_InProgress and call them conditionally from within this proc, you shouldn't have any issues.
You could be the victim of "parameter sniffing" problem. MS-SQL will take the parameters of your 1st run of your SP as the best sampling for making the query plan. Your query could be slow due to this.
To prove, try to run the content of your query directly, by simulating the populated parameters as variables. If it is much faster, then you are indeed having the "parameter sniffing" problem.
The solution is to trick the MS-SQL to think that your parameters are only being used to be assigned to another variables. Example:
create proc ManyParams
(
#pcol1 int,
#pcol2 int,
#pcol3 int
)
as
declare
#col1 int,
#col2 int,
#col3 int
select
#col1 = #pcol1,
#col2 = #pcol2,
#col3 = #pcol3
select
col1,
col2,
col3
from
tbl
where
1 = case when #col1 is null then 1 else case when col1 = #col1 then 1 else 0 end end
and 1 = case when #col2 is null then 1 else case when col2 = #col2 then 1 else 0 end end
and 1 = case when #col3 is null then 1 else case when col3 = #col3 then 1 else 0 end end
here is a great article on this topic:
http://www.sommarskog.se/dyn-search-2005.html
It will give you a lot of ideas to try out.
I tend to to a mix of the things to make these "search" type queries go fast. Here are some that I seem to use all the time:
I try to make certain search parameters required, so you can hit the index on those.
if possible (depends on number of rows) split up the query, using temp tables. IF you only have a few hundred ClientID values, create a #ClientID temp table. Put in the one the user wants, or all of them. You can then make this the FROM table and/or inner join other tables to this to make it much faster.
If you have dates that are optional, don't use anything like (#startDate is null or a.date >= #startDate). Just do something like SET #startDate=COALESCE(#startDate,'01/01/1970'). This will give you a value and eliminate using an "OR" and will use an index.
casperOne's suggestion is what I would start with.
One other possibility is this:
WHERE
(1 =
CASE
WHEN #client_id IS NULL THEN 1
WHEN a.clientID = #clientID THEN 1
ELSE 0
END) AND
...
I found that SQL Server (at least 2005) using the CASE statement like this can cause the query plan to short-circuit the rest of the logic. In the case of simple comparisons that's not really a big problem, but if your logic includes a subquery or some other expensive operation it might be a big help to short-circuit it. In your example, I would just go with casperOne's suggestion though.
Also, if you use the CASE method above, you'll need to add the RECOMPILE options to your SELECT and your stored procedure.