89

I am creating a SQL query in which I need a conditional where clause.

It should be something like this:

SELECT 
    DateAppr,
    TimeAppr,
    TAT,
    LaserLTR,
    Permit,
    LtrPrinter,
    JobName,
    JobNumber,
    JobDesc,
    ActQty,
    (ActQty-LtrPrinted) AS L,
    (ActQty-QtyInserted) AS M,
    ((ActQty-LtrPrinted)-(ActQty-QtyInserted)) AS N
FROM 
    [test].[dbo].[MM]
WHERE 
    DateDropped = 0
            --This is where i need the conditional clause 
    AND CASE
            WHEN @JobsOnHold = 1 THEN DateAppr >=  0
            ELSE  DateAppr != 0
        END

The above query is not working. Is this not the correct syntax or is there another way to do this that I don't know?

I don't want to use dynamic SQL, so is there any other way or do I have to use a workaround like using if else and using the same query with different where clauses?

numaroth
  • 1,295
  • 4
  • 25
  • 36

6 Answers6

114

Try this

SELECT 
    DateAppr,
    TimeAppr,
    TAT,
    LaserLTR,
    Permit,
    LtrPrinter,
    JobName,
    JobNumber,
    JobDesc,
    ActQty,
    (ActQty-LtrPrinted) AS L,
    (ActQty-QtyInserted) AS M,
    ((ActQty-LtrPrinted)-(ActQty-QtyInserted)) AS N
FROM 
    [test].[dbo].[MM]
WHERE 
    DateDropped = 0
    AND (
    (ISNULL(@JobsOnHold, 0) = 1 AND DateAppr >= 0) 
    OR 
    (ISNULL(@JobsOnHold, 0) != 1 AND DateAppr != 0)
    )

You can read more about conditional WHERE here.

Mahesh
  • 8,694
  • 2
  • 32
  • 53
  • 6
    As your table grows, you'll want to watch performance of the query that you end up with, because the optimizer can become pretty unhappy with these types of queries. – GaTechThomas Jan 13 '15 at 16:41
  • 2
    Instead of `(ISNULL(@JobsOnHold, 0) = 1` you could use `@JobsOnHold IS NULL` or `IS NOT NULL` – KyleMit Apr 15 '19 at 20:42
  • 1
    @GaTechThomas , Any solution ? – Shaiju T Nov 13 '20 at 11:12
  • 2
    @shaijut these sorts of things tend to be indicators of need for a different approach. Consider a materialized view that specifically serves the query at hand, or, if you have the flexibility, an approach that copies the data over into a separate area that gives the read side more options (such as CQRS). – GaTechThomas Nov 13 '20 at 22:42
27

Try this one -

WHERE DateDropped = 0
    AND (
        (ISNULL(@JobsOnHold, 0) = 1 AND DateAppr >= 0) 
        OR 
        (ISNULL(@JobsOnHold, 0) != 1 AND DateAppr != 0)
    )
ypercubeᵀᴹ
  • 113,259
  • 19
  • 174
  • 235
Devart
  • 119,203
  • 23
  • 166
  • 186
  • Just a query According to this[link](dba.stackexchange.com/a/5337/27862) answer there may be case when the both conditions will get resolved in AND operator use so if first one fails then also it can apply the DateAppr= 0 condition right ? –  Sep 05 '13 at 06:44
  • 2
    The OP had changed the code - a less than trivial change - and made our answers look rather dumb :) – ypercubeᵀᴹ Sep 05 '13 at 15:58
16

To answer the underlying question of how to use a CASE expression in the WHERE clause:

First remember that the value of a CASE expression has to have a normal data type value, not a boolean value. It has to be a varchar, or an int, or something. It's the same reason you can't say SELECT Name, 76 = Age FROM [...] and expect to get 'Frank', FALSE in the result set.

Additionally, all expressions in a WHERE clause need to have a boolean value. They can't have a value of a varchar or an int. You can't say WHERE Name; or WHERE 'Frank';. You have to use a comparison operator to make it a boolean expression, so WHERE Name = 'Frank';

That means that the CASE expression must be on one side of a boolean expression. You have to compare the CASE expression to something. It can't stand by itself!

Here:

WHERE 
    DateDropped = 0
    AND CASE
            WHEN @JobsOnHold  = 1 AND DateAppr >= 0 THEN 'True'
            WHEN DateAppr != 0 THEN 'True'
            ELSE 'False'
        END = 'True'

Notice how in the end the CASE expression on the left will turn the boolean expression into either 'True' = 'True' or 'False' = 'True'.

Note that there's nothing special about 'False' and 'True'. You can use 0 and 1 if you'd rather, too.

You can typically rewrite the CASE expression into boolean expressions we're more familiar with, and that's generally better for performance. However, sometimes is easier or more maintainable to use an existing expression than it is to convert the logic.

Bacon Bits
  • 30,782
  • 5
  • 59
  • 66
7

The problem with your query is that in CASE expressions, the THEN and ELSE parts have to have an expression that evaluates to a number or a varchar or any other datatype but not to a boolean value.

You just need to use boolean logic (or rather the ternary logic that SQL uses) and rewrite it:

WHERE 
    DateDropped = 0
AND ( @JobsOnHold = 1 AND DateAppr >= 0 
   OR (@JobsOnHold <> 1 OR @JobsOnHold IS NULL) AND DateAppr <> 0
    )
ypercubeᵀᴹ
  • 113,259
  • 19
  • 174
  • 235
  • 1
    Just a query According to this [link] (http://dba.stackexchange.com/a/5337/27862) answer there may be case when the both conditions will get resolved in AND operator use so if first one fails then also it can apply the DateAppr= 0 condition right ? –  Sep 05 '13 at 06:44
  • 2
    Not sure what you are asking. The question/answer there is about short-circuiting. You really shouldn't worry about that - unless you have some performance problem. – ypercubeᵀᴹ Sep 05 '13 at 06:48
6

Often when you use conditional WHERE clauses you end upp with a vastly inefficient query, which is noticeable for large datasets where indexes are used. A great way to optimize the query for different values of your parameter is to make a different execution plan for each value of the parameter. You can achieve this using OPTION (RECOMPILE).

In this example it would probably not make much difference, but say the condition should only be used in one of two cases, then you could notice a big impact.

In this example:

WHERE 
    DateDropped = 0
    AND (
    (ISNULL(@JobsOnHold, 0) = 1 AND DateAppr >= 0) 
    OR 
    (ISNULL(@JobsOnHold, 0) <> 1 AND DateAppr <> 0)
    )
OPTION (RECOMPILE)

Source Parameter Sniffing, Embedding, and the RECOMPILE Options

wezzix
  • 1,984
  • 1
  • 19
  • 18
2

This seemed easier to think about where either of two parameters could be passed into a stored procedure. It seems to work:

SELECT * 
FROM x 
WHERE CONDITION1
AND ((@pol IS NOT NULL AND x.PolicyNo = @pol) OR (@st IS NOT NULL AND x.State = @st))
AND OTHERCONDITIONS
Gordon Prince
  • 142
  • 2
  • 9