SQL Server 2008 - Conditional Query - sql

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!

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)

WHERE coalesce(active,1) = (CASE
WHEN #isActive = -1 THEN coalesce(active,1)
ELSE #isActive
END)

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.

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)

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

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.

Related

How to implement nested CASE statements in a SQL Select of a stored procedure?

I have a SELECT statement from a temp table in a stored procedure that selects these two columns:
DECLARE #Mode INT
CASE
WHEN t.Descr = '-- Prior Balance --'
THEN ''
ELSE t.ChgAmount
END AS ChgAmount,
CASE
WHEN t.Descr = '-- Prior Balance --'
THEN ''
ELSE t.PayAmount
END AS PayAmount,
I want to conditionally return those two columns differently depending on the value of #Mode, specifically if it is equal to 7.
I'm getting confused about the levels of nesting that I need and the formatting of doing this.
So far I have tried something like this:
CASE
WHEN #Mode = 7
THEN
CASE
WHEN t.TranDate > #Due
THEN t.ChgAmount
END
END AS CurrentCharges,
CASE
WHEN t.Descr = '-- Prior Balance --'
THEN ''
ELSE t.ChgAmount
END AS ChgAmount,
CASE
WHEN t.Descr = '-- Prior Balance --'
THEN ''
ELSE t.PayAmount
END AS PayAmount,
The above SELECT might work, but it would stil return the extra column. How should I nest the other, original, CASE statement for the ChgAmount?
First, let's clarify in abstract terms that in the following pseudo-code
If X then
Case 1
If Y then
Case 2
Else
Case 3
End If
Else
Case 4
End If
Case 1 is equivalent to "X is true"
Case 2 is equivalent to "X is true and Y is true"
Case 3 is equivalent to "X is true and Y is false"
Case 4 is equivalent to "X is false"
Furthermore, let's clarify that case-when criterias are logically very similar to our pseudo-code presented above of if-then, hence, you can apply composite criteria instead of nesting case-when if you prefer that, but also, you can implement nested case-when criterias, it's a matter of style.
As a result, you will need to formulate the logic you want to apply, by asking yourself the following questions:
do I need the same number of fields in my different cases? (if not, then you will probably need to write different queries in different cases)
what cases do I have for my fields if #Mode is 7?
what cases do I have for my fields if #Mode is not 7?
how can I merge my criterias in the points above into coherent (composite) criterias that would not require nesting?
If you answer these questions as an edit to this question, then we will be able to more properly answer your questions than the general terms I'm using in this answer and we may provide code for you as well. However, if you think this through, then you might also be able to implement this in a not nested way. And, if you are able to understand this as far as to implement it into a not nested way, then you could transform that implementation into a nested implementation as well.

Setting BIT variable using CASE statements

I'm in the middle of a migration that uses some reporting elements from Crystal and attempting to convert what existed previously into SQL. It looks to use VB scripting but I can't figure out how to make the switch over.
Initially I'd assume a CASE WHEN statement would suffice but I can't determine the right logic behind the query.
Sample of the VB below:
Dim HasValue As Boolean
If isnull({Reference.Shallow}) Then
HasValue=False
formula="MISSING"
Else
HasValue=True
End If
I am aware that IF does exist in SQL Server but when researching it, people tend to stray away from it as CASE WHEN does the same?
I have some psuedo that I'd imagine should work in the same way but this does not resolve as you cannot set a variable within a CASE WHEN statement (I believe):
DECLARE HasValue BIT
CASE WHEN Reference.Shallow IS NULL
THEN SET #HasValue = 1
ELSE SET #HasValue = 0
END AS Shallow
What would be the most appropriate way of doing this within SQL?
You must use this code snippet
DECLARE #HasValue BIT
SELECT #HasValue=CASE WHEN Shallow IS NULL THEN CAST(1 AS BIT) ELSE CAST(0 AS
BIT) END FROM Reference.Shallow
CASE returns a scalar value, not a operation or boolean result. You set the value of the variable to the result of the CASE:
SET #HasValue = (SELECT CASE WHEN R.Shallow IS NULL THEN 1 ELSE 0 END
FROM dbo.Reference R
/*WHERE...? */);

SQL CASE statement needs to handle Text

Apologies if this has been asked before - I've spent a couple of hours searching but not found anything that's helped.
It's quite simple really - I've been asked to create a query which includes a field that when it was set up (not by me) was created as a VARCHAR instead of an INT.
I need to do some calculations on this field, however some users have been entering text into it, so the calculations fail as it can't convert the data to an INT.
Is there anything I can add to a CASE statement to handle where there's text?
I was thinking something like the below, but don't know what the actual code is:
CASE
WHEN [Field1] IS TEXT THEN 1 ;
ELSE [Field2] as [Chose name]
END
Edit: Note that this is in MS SQL Server.
Thanks.
In SQL Server, you can use try_convert() and isnull() for this:
isnull(try_convert(int, field), 1)
try_convert() attempts you cast field to an int. When that fails, null is returned; you can trap that with isnull() and turn the result to 1 instead.
Note that this only works as long as field is not null (otherwise, you would get 1 as a result).
In SQL Server
Declare #Salary varchar(100);
Set #Salary = 50000;
Select Case when ISNUMERIC(#Salary) = 1 then 1
else 0 end as [Check]
May be this will be Helpful.

In SQL is there any performance between using like vs equals?

I am using T-SQL (SQL Server).
This is the starting code:
DECLARE #code as char(8)
SET #code = '123' -- 123%'
DECLARE #lsUseLikeForCodeWhereCondition char(1)
IF CHARINDEX('%', #code) = 0
SET #lsUseLikeForCodeWhereCondition = 'N'
ELSE
SET #lsUseLikeForCodeWhereCondition = 'Y'
Is there any performance between these two statements:
select * from mytable where idn_orn_i LIKE
CASE WHEN #lsUseLikeForCodeWhereCondition = 'N' THEN
#code
ELSE
#code + '%'
END
vs
IF #lsUseLikeForCodeWhereCondition = 'N'
BEGIN
select * from mytable where idn_orn_i = #code
END
ELSE
BEGIN
select * from mytable where idn_orn_i Like #code + '%'
END
Both appear to return the same results. But where it says mytable. It is actually a join with 10 different tables. So it is not small. Mostly I am wondering if the optimizer would recognize a like WITHOUT a percent sign in the string and do an equals.
If it matters idn_orn_i is char(8).
The two versions are very different and this has little to do with any differences between like and =. In the first, the pattern is an expression-based pattern, which precludes the use of indexes for the query.
In the second, you have two queries and in each, the comparisons are using constants. So, the first version will definitely use an appropriate index. And I think the second will also take advantage of an index too (the compiler should turn the constant expression into a constant so an index can be used). Note that like requires that the pattern not start with a wildcard and probably has some conditions on the collations as well.
Short answer, if you can use the =, use the =.
If the same number of rows is being returned, it should be the same cost.
The actual execution plans should more or less be equal. That being said, if you are saying where value = 'asdf' vs value like 'asdf%' and you have have a lot more rows that will be returned with the like vs the =, then the = would be faster. It would also vary based on the number of rows that need to be scanned/looked up. Which depends on your data distribution statistics or cardinality.
You should look at the execution plans to know for sure.
Exact matches, =, tend to perform faster because the string search can be potentially invalidated from a check with just the first and/or last characters.
A like cannot discount the match from the first character if it is non-matching, for cases when a % is placed before the pattern of interest. It would search the string sequentially, potentially to the end of the string, until the match is found or not at all. Thus, is more time consuming, but useful and necessary for certain tasks.
A like, without any wild cards, would operate in the same manner as =, but the procedure for = is intrinsically more efficient.

Using IF/THEN or CASE within a WHERE clause in SQL

I have been assigned the task of updating the EEO survey and reporting for a mid sized company. I am working on a stored procedure to populate a report from. All is good but for a syntax problem. One of the requirements is to dynamically allow the user to filter the results by the EEO Job Group Number. When the report page loads, it populates the table with all Job Groups Combined. I have placed a DropDownList on the page that allows the user to choose one of the 10 EEO Job Groups or by default, All Job Groups Combined (no filtering). The DDL executes postback and populates a parameter; #intEeoJobGroupID. There is not actually a 0 ID value in the table, just in the DDL. I want the (usp) query to use one set of WHERE statements if the passed parameter #intEeoJobGroupID = 0, and another if #intEeoJobGroupID <> 0. (Effectively adding another AND statement if the parameter <> 0)
I want to return the count of how many EEO records meet the requirements of the query. I have tried IF/THEN, and CASE, in many different formats, and can not seem get the syntax right. In the example below I get the message "Incorrect Syntax near the first = in the THEN statement, as well as the keyword ELSE.
Any hints?
DECLARE #intEeoJobGroupID INT
SELECT
COUNT (E.intEeoID)
FROM
dbo.NewEEO AS E
WHERE
CASE WHEN #intEeoJobGroupID = 0
THEN
E.intGenderID = 1
AND E.intRaceID = 2
ELSE
E.intGenderID = 1
AND E.intRaceID = 2
AND E.intEeoJobGroupID = #intEeoJobGroupID
You're making it way too complicated:
WHERE E.intGenderID = 1
AND E.intRaceID = 2
AND (E.intEeoJobGroupID = #intEeoJobGroupID OR #intEeoJobGroupID = 0)
As someone else already mentioned, your existing syntax was missing an "END", but it still won't work with that added. To get this right in the future, one thing you can try to do is remember that CASE expressions in SQL are just that: expressions. They are not statements, as you might be used to with if statements in c# code. You don't use CASE for flow control, to define blocks as you were trying to do.
Don't try to return a boolean from a CASE statement. Instead return some value that is then checked outside the CASE statement (and so then resulting in a boolean).
CASE WHEN #mode = 1 THEN CASE WHEN <Condition1> THEN 1 ELSE 0 END
WHEN #mode = 2 THEN CASE WHEN <Condition2> THEN 1 ELSE 0 END
END
=
1
Note: This will create Awful execution/explain plans and totally nerf performance. You are better using real IF blocks and real queries, or possibly unions...
IF #mode = 1
SELECT foo FROM bar WHERE <Condition1>
ELSE IF #mode = 2
SELECT foo FROM bar WHERE <Condition2>
Or...
SELECT foo FROM bar WHERE <condition1> AND #mode = 1
UNION ALL
SELECT foo FROM bar WHERE <condition2> AND #mode = 2
In order to prevent massive duplication of code, you may find that encapsulating the bulk of the query in a VIEW is helpful.
You can't make a comparison the result of a case condition. If you're using case in a where clause, it needs to be on one side of the operator:
CASE #case_value
WHEN 0 THEN
some_column
ELSE
some_other_column
END = #some_value
However, if you try to make your actual condition fit this rule, you'll end up not using the case statement at all, as #Joel point out.
You have to add
end
in the end of case.