views:

169

answers:

6

Hello,

SQL is not one of my strong suits. I have a SQL Server 2008 database. This database has a stored procedure that takes in eight int parameters. For the sake of keeping this question focused, I will use one of these parameters for reference:

@isActive int

Each of these int parameters will be -1, 0, or 1. -1 means "Unknown" or "Don't Care". Basically, I need to query a table such that if the int parameter is NOT -1, I need to consider it in my WHERE clause. Because there are eight int parameters, an IF-ELSE statement does not seem like a good idea. At the same time, I do not know how else to do this?

Is there an elegant way in SQL to add a WHERE conditional if a parameter does NOT equal a value?

Thank you!

+1  A: 
WHERE coalesce(active,1) = (CASE 
                              WHEN @isActive = -1 THEN coalesce(active,1)
                              ELSE @isActive
                            END)
dcp
yeah. sadly there are more than 8 cases - there are 8! cases, which will be a monster query.
TomTom
@TomTom, one `CASE` statement per parameter. I don't see the problem.
Marcus Adams
Brain usage? it is 8 parameters, NOone says ONLY ONE is unwanted. One irrelevant? 8 combinations. 2 irrelevant? 3 irrelevant? That is a LOT more than 8 different cases.
TomTom
@TomTom, it's a partial answer. The full solution would use 8 CASE statements, not one CASE statement with 8! expressions.
Marcus Adams
try this out: `if null = null print 'wow'`, you'll notice that "wow" never prints. if you pass in @isActive as -1 you'll not get any rows back where the active column is NULL. As a result this does not work if your column can be null.
KM
@KM - Good point, I edited my answer to take this into account.
dcp
this now has no chance of an index being used, even if you are on the proper version of SQL Server 2008 and use `OPTION (RECOMPILE)`
KM
@KM - Sure, but your accepted answer also doesn't handle the case where you want to be able to pass null in and use that as a search condition, which is desirable sometimes. So pick your poison.
dcp
@dcp, mine will work if you pass in NULL: `(@search1 IS NULL or Column1=@Search1)`. Try it out: `DECLARE @x int; SET @x=null; IF (@x IS NULL or 5=@x) PRINT 'wow'` will always print 'wow', try it where you `SET @x=5`. The fundamental difference between your way is that you always compare the column to your `CASE`. I use the `OR` to handle the `NULL` situation and the `=` situation. The first part of the `OR` compares the given parameter to NULL if it is null, the row is selected regardless of the column's value. The second half of the OR checks for an exact match between the column and param.
KM
@KM - I think maybe you misunderstood what I was saying. What if I *want* a search condition like this in my query: "Column1 is null"? So really, there are three conditions here that we could have: I want to search on Column1 using @search1 (ex. @search1 = 'wow'), I don't want to use @search1 at all in my search, or thirdly, I want to have a condition where Column1 = null. There's no way to handle all three with a single @search parameter. All I'm saying is that you don't handle the third condition, unless I've misunderstood. The way to handle it is to add another parameter (Column1IsNull).
dcp
You could modify mine to be `(@search1 IS NULL or Column1=@Search1 or (@search1null='Y' AND Column1 IS NULL))` provided that you add a new parameter `@search1null` that when it is `Y` then you want only null values. but there are an unlimited number of ways to search that the OP has not asked for that anyone can throw up and say your solution does not address.
KM
@KM Agreed, I was just pointing this out because searching using a null condition is fairly common.
dcp
A: 

.... Where field=case @isActive WHEN -1 THEN field ELSE @isActive END ....

Alexander
bad. Make some basic math on how many combinations you need - you will be surprised. This will be a monster stored procedure for no gain.
TomTom
What do you mean? This works very well with very big queries with a lot of parameters.
Alexander
Really? 8 optional parameters - how many where / when do you get? ;) Little hint: it does not say only one parameter is optional - what about all the combinations of 2 optional ones, 3 optional ones, 4 optional ones?
TomTom
try this out: `if null = null print 'wow'`, you'll notice that "wow" never prints. if you pass in @isActive as -1 you'll not get any rows back where the field column is NULL. As a result this does not work if your column can be null.
KM
TomTom, what's the problem? You write a case statement for each parameter. Liniar complexity of the query text.KM, this case was not specified by the asker. In this case a isnull() would be appropriate. Coalesce is also good, but it is the same isnull() only for more variables, as it generates a isnull(@var,isnull(@var2,@var3)) for coalesce(@var,@var2,@var3).
Alexander
A: 

There is NOT an elegant way - all ways suck.

WHERE @isActive == -1 OR isActive = @isActive

is basically the only way - but even then you please make sure that the query plan is reevaluated every time, otherwise most queries will use the wrong query plan.

THis is a classical case where stored procedures are bad. Querying should IMHO not be done using stored procedures at all since modern times - which started about 15 years ago when someone was smart enough to write the first ORM.

TomTom
Stored procedures are good if you have to limit the access to the actual tables, provide them ready to use data through stored procedures.
Alexander
Really? How is that better than a view that STILL ALLOWS THE USER TO DEFINE QUERY PARAMETERS DYNAMICALLY?Read http://weblogs.asp.net/fbouma/archive/2003/11/18/38178.aspx for a basic education about how badly flawed your logic is.
TomTom
TSQL doesn't use "==", see [Comparison Operators (Transact-SQL)](http://msdn.microsoft.com/en-us/library/ms188074.aspx), I think you wanted to say `WHERE @isActive = -1 OR isActive = @isActive`
KM
Views are much more limited in functionality than procedures. For instance you can make a procedure return a value, from which you can determine whether you have or haven't got an error. Also, procedures allow the usage of complex algorithms not only for viewing data, but also for modifying it. Stop being such an arrogant developer, and look at what other practices exist, you might actually learn something, rather than sticking with your fobia of stored procedures.
Alexander
Stop making an idiot out of you. A view allows me to define filter conditions post faktum AND allows me to do all the same things a procedure does, too (even with possibly a trick). Flexibility in queries is king. I did stored procedures 20 years ago - these days I value having not to maintain them in 90% of the applications. Saved time = saved money.
TomTom
The only one who's making a fool of himself is you. For a troll like you I had to search through the manual, AGAIN, read it, AGAIN, and find out, AGAIN, that views are for viewing data, because views are views, they are not supposed to do anything that let you view.I am appalled! 20 years in the business and still haven't learned anything?!! Must really suck being you.http://msdn.microsoft.com/en-us/library/aa258253(SQL.80).aspx
Alexander
+4  A: 

best source for dynamic search conditions:

Dynamic Search Conditions in T-SQL by Erland Sommarskog

there are a lot of subtle implications on how you do this as to if an index can be used or not. If you are on the proper release of SQL Server 2008 you can just add OPTION (RECOMPILE) to the query and the local variable's value at run time is used for the optimizations.

Consider this, OPTION (RECOMPILE) will take this code (where no index can be used with this mess of ORs):

WHERE
    (@search1 IS NULL or Column1=@Search1)
    AND (@search2 IS NULL or Column2=@Search2)
    AND (@search3 IS NULL or Column3=@Search3)

and optimize it at run time to be (provided that only @Search2 was passed in with a value):

WHERE
    Column2=@Search2

and an index can be used (if you have one defined on Column2)

KM
@Villager, you'll substitute NULL for -1, and be good to go.
Marcus Adams
@Marcus Adams, yes you are correct. I'm in the habit of passing in NULL for "ignore" search conditions, so I just coded the example that way. So based on the OPs question code them like `(@Search1=-1 OR Column1=@Search1) AND ...`
KM
KM, seems you are the only smart one except my post among idiots that dont understand the implications of what they write here. Grats - especially for the option(recombile) which I did not bother looking up (I never do queries via stored procedures to start with).
TomTom
+1  A: 

The pattern (column = @param OR @param IS NULL) will give you optional parameters. You can use NULLIF to neutralize your -1. Even better would be to allow null parameters, instead of using a magic number.

WHERE
   (Customer.IsActive = NULLIF(@isActive, -1) OR NULLIF(@isActive, -1) IS NULL)
Anthony Faull
You have 4 "(", but only 3 ")", if you change it to `(Customer.IsActive = NULLIF(@isActive, -1) OR (NULLIF(@isActive, -1) IS NULL))` it will work.
KM
@KM Quite right. I've removed the extra bracket now.
Anthony Faull
+1  A: 

Rather than using -1 to signify that you don't know or don't care, how about just using Null for that? Pretty much what it was made for. Then you could switch to a Bit rather than an Int.

Also, I'm sure TomTom will disagree, but I think using a CASE statement is the way to go for this stuff.

Your mileage may vary, but it seems that the query engine handles it a lot better than wrapping things in IsNull or having multiple OR statements, which can get rather messy as you start adding other conditions.

No matter which way you go, the execution plan is going to suffer a little bit depending on what you're passing in, but it shouldn't be TOO horrible.

The extra benefit of going with CASE statements is that you can add a bit of complexity without much extra code (versus going with a bunch of OR statements). Also, the first condition to match your criteria can prevent the extra evaluations, which isn't always the case when dealing with OR's...

So, for 8 optional parameters with -1 as the value use to ignore the search, what you end up with is something along the lines of:

WHERE
        @Search1 = CASE WHEN @Search1 = -1 THEN @Search1 ELSE @Column1 END
    AND @Search2 = CASE WHEN @Search2 = -1 THEN @Search1 ELSE @Column2 END
    AND @Search3 = CASE WHEN @Search3 = -1 THEN @Search1 ELSE @Column3 END
    AND @Search4 = CASE WHEN @Search4 = -1 THEN @Search1 ELSE @Column4 END
    AND @Search5 = CASE WHEN @Search5 = -1 THEN @Search1 ELSE @Column5 END
    AND @Search6 = CASE WHEN @Search6 = -1 THEN @Search1 ELSE @Column6 END
    AND @Search7 = CASE WHEN @Search7 = -1 THEN @Search1 ELSE @Column7 END
    AND @Search8 = CASE WHEN @Search8 = -1 THEN @Search1 ELSE @Column8 END

NOTE: As KM pointed out, the NULL method falls short if the columns you're working will can potentially have NULL values, since NULL=NULL won't evaluate properly. So, for fun, I changed my answer back to what the original poster requested, which is to use their own identifier for skipping the search.

Kevin Fairchild
Gosh, ever tried that on a real database? Not too horrible? Depends TOTALLY On which query plan is stored - I had cases where things turned into 3 minute monsters with table scans thanks to the first query ending up with a table scan.
TomTom
And using OR's instead of CASE's fixed that? If you don't have proper indexing and you have a lot of data, you can run into problems. I've run queries like this on tables with millions of records. Certainly doesn't take minutes. But, like I said, your mileage may vary. Depends a lot on table design, what's getting queried, how much data, and what kind of data. There's many ways to approach the issue. Just don't understand why you're being so critical of every solution other than your own.
Kevin Fairchild
try this out: `if null = null print 'wow'`, you'll notice that "wow" never prints. if you pass in @search1 as NULL you'll not get any rows back where the Column1 column is NULL. As a result this does not work if your column can be null.
KM