case statement and where clause

  • I am trying to find more efficient way of writing this query.

    There are two input parameters eg. @Type (4 different Type + Null

    ) and @value.

    Depending on the Type and value data retrieve is filtered.

    Select * from tableA

    where case when @Type = Lname then Lname = @Value

    when @type = fName then Fname = @Value

    and so on....

    Currently I have a logic in place as if then else...

    If @Type = 'Lname'

    Begin

    select * from tableA

    where Lname = @Value

    End

    else If @Type = 'Fname'

    Begin

    select * from tableA

    where Fname = @Value

    end

    else if @type is null

    begin select * from TableA

    end

    TIA,

    Natasha

  • the following should help:

    Select *

    from tableA

    where case

    when @Type = 'Lname' then Lname

    when @Type = 'fName' then Fname

    when @Type is null then .....

    else .....

    end = @Value

    Notice how the @Type evaluates to a column name, then the entire case statement is then compared to @value

  • Natasha:

    That's actually not a bad way to go. Your instinct might be to combine all of these into one big master query that uses the parameters to logically switch the WHERE conditions, but that is almost always a bad idea. Logical search conditions that get "switched" on and off are not handled well by the query optimizer because it has to come up with one plan that covers all of these possible conditions. Consequently, it tends to pick a plan that always runs slowy, but always works, like a table scan. So ultimately, using IF to switch the search conditions is actually better than using CASE or other WHERE expressions.

    The only problem is that having all of these different queries together in one stored procedure might result in an execution plan that does not take all of them into account well, however, most of the time that I have checked one of these they seemed to use the right query plan.

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • RBarryYoung (5/15/2009)


    Natasha:

    Logical search conditions that get "switched" on and off are not handled well by the query optimizer because it has to come up with one plan that covers all of these possible conditions. Consequently, it tends to pick a plan that always runs slowy, but always works, like a table scan. So ultimately, using IF to switch the search conditions is actually better than using CASE or other WHERE expressions.

    I'm totally agree with Barry.... using the IF is a better approach ....

  • Here's a script demonstrating the problem, using your current code example, Natasha, along with the code that Sam provided showing how to get CASE into the WHERE clause:

    --====== First Create TableA and add indexes: ======

    Create Table TableA (

    ID int identity(1,1) primary key

    , Lname varchar(255)

    , Fname varchar(255)

    )

    Create index idxTableA_Fname on TableA(Fname)

    Create index idxTableA_Lname on TableA(Lname)

    go

    --====== Now Load it with data: ======

    Insert into TableA (Lname, Fname)

    Select [name], [name]

    from master.sys.syscolumns

    go

    --====== Create the IF based stored procedure ======

    Create Proc spGet_TableA(@type as varchar(20), @value varchar(255))

    AS

    Begin

    If @Type = 'Lname'

    Begin

    select * from tableA

    where Lname = @Value

    End

    else If @Type = 'Fname'

    Begin

    select * from tableA

    where Fname = @Value

    end

    else if @type is null

    begin select * from TableA

    end

    End

    go

    --====== Create the CASE based stored procedure ======

    Create Proc spGet_TableA_case(@type as varchar(20), @value varchar(255))

    AS

    Begin

    Select *

    from tableA

    where case

    when @Type = 'Lname' then Lname

    when @Type = 'fName' then Fname

    when @Type is null then Null

    end = @Value

    End

    go

    --====== Now Execute Both ======

    --(actually, you should select these lines and user "Control-L"

    -- to display the query plans)

    EXEC spGet_TableA 'Lname', 'id'

    go

    EXEC spGet_TableA_case 'Lname', 'id'

    go

    If you display the query plans for the two EXEC's at the end, you will see that while you original query uses a different index, depending on the @Type parameter, the combined query has a plan that just always scans the table.

    I have attached the query plans from my system for convenience.

    (EDIT: the attachment had the wrong plan in it. I have corrected this.)

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • Thank you to both of you. I was thinking the same that using if then else will be more efficient than case since case does not use indexes and majority of the time table scan is performed. With case code has clarity in reading as oppose to bunch of if then else statements but in programming world efficiency is a priority.

    Once again thank you both for convincing me 🙂 about my original code

    Natasha

  • You could also consider the option of dynamic querying

    create table tableA (Lname varchar(10), Fname varchar(10))

    insert into tableA (Lname,Fname) select 'a1','b1' union select 'a2','b2'

    --select * from tableA

    declare @Type varchar(10),

    @value varchar(10),

    @sqlstr nvarchar(100);

    set @Type = 'Lname';

    set @value = 'a2';

    set @sqlstr = 'select * from tableA ';

    If @Type = 'Lname'

    Begin

    set @sqlstr = @sqlstr + ' where Lname = ''' + @value + ''''

    End

    else If @Type = 'Fname'

    Begin

    set @sqlstr = @sqlstr + ' where Fname = ''' + @value + '''';

    end

    print @sqlstr

    exec sp_executesql

    @stmt= @sqlstr,

    @params= N'@value varchar(255)',

    @value= @value;

  • gyessql (5/15/2009)


    You could also consider the option of dynamic querying

    I am a big proponent of dynamic SQL, but I would not recommend it, because it not necessary for these cases unless the IF..ELSE structure proves inadequate. Plus, dynamic SQL introduces other problems, especially having to protect against SQL Injection attacks.

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • We can use case statement in where clause as follows:

    declare @x int

    Select col1,col2

    from table1

    where col1 = case when @x = 1 then 1000

    when @x = 2 then 5000

    end

    Regards,
    Tatoba

  • One more alternative, that is optimized well:

    SELECT *

    FROM TableA

    WHERE @Type = 'Lname'

    AND Lname = @Value

    UNION ALL

    SELECT *

    FROM TableA

    WHERE @Type = 'Fname'

    AND Fname = @Value

    UNION ALL

    SELECT *

    FROM TableA

    WHERE @Type IS NULL;

    The constant checks on @Type become start-up filters in the plan, so the parts of the UNION that don't match at run time are not actually executed at all.

  • Nice idea Paul. I've tried some things like this in the past, but I could never seem come up with a query pattern that would be retain a stable optimization across a large number of cases. Do you know if your approach works on both 2005 and 2008? (I had a lot more trouble with my attempts on 2005 than on 2008)

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

  • RBarryYoung (3/27/2010)


    Nice idea Paul. I've tried some things like this in the past, but I could never seem come up with a query pattern that would be retain a stable optimization across a large number of cases. Do you know if your approach works on both 2005 and 2008? (I had a lot more trouble with my attempts on 2005 than on 2008)

    Yes it works on both, but is definitely better with simpler plans. I used to use this pattern a fair bit with 2005, less so these days. As always, it all depends on the details. Both methods need careful consideration, particularly concerning parameter sniffing. If the UNIONed queries are relatively simple, they would often be accompanied by an OPTION (RECOMPILE).

  • Thanks, Paul, good to know. 🙂

    [font="Times New Roman"]-- RBarryYoung[/font], [font="Times New Roman"] (302)375-0451[/font] blog: MovingSQL.com, Twitter: @RBarryYoung[font="Arial Black"]
    Proactive Performance Solutions, Inc.
    [/font]
    [font="Verdana"] "Performance is our middle name."[/font]

Viewing 13 posts - 1 through 12 (of 12 total)

You must be logged in to reply to this topic. Login to reply