views:

17216

answers:

5

I'm creating a stored procedure to return search results where some of the parameters are optional.

I want an "if statement" in my where clause but can't get it working. The where clause should filter by only the non-null parameters.

Here's the sp

ALTER PROCEDURE spVillaGet 
-- Add the parameters for the stored procedure here
@accomodationFK int = null,
@regionFK int = null,
@arrivalDate datetime,
@numberOfNights int,
@sleeps int = null,
@priceFloor money = null,
@priceCeil money = null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

-- Insert statements for procedure here
select tblVillas.*, tblWeeklyPrices.price from tblVillas
INNER JOIN tblWeeklyPrices on tblVillas.villaId = tblWeeklyPrices.villaFK
where 
 If @accomodationFK <> null then
  accomodationTypeFK = @accomodationFK 
  @regionFK <> null Then
  And regionFK = @regionFK 
 IF @sleeps <> null Then
  And sleeps = @sleeps 
 IF @priceFloor <> null Then
  And price >= @priceFloor And price <= @priceCeil


END

Any ideas how to do this?

+9  A: 
select tblVillas.*, tblWeeklyPrices.price 
from tblVillas
INNER JOIN tblWeeklyPrices on tblVillas.villaId = tblWeeklyPrices.villaFK
where (@accomodationFK IS null OR accomodationTypeFK = @accomodationFK)
  AND (@regionFK IS null or regionFK = @regionFK)
  AND (@sleeps IS null OR sleeps = @sleeps)
  AND (@priceFloor IS null OR (price BETWEEN @priceFloor And @priceCeil))
Ovidiu Pacurar
thanks for your help but the value in the database is not null. I want to skip that part of the where clause if null is passed in for one of the params
mancmanomyst
Why this works: if @var is null, then the whole line is true, regardless of the rest of the line (thus ignored and unfiltered). If it's not null, than the line is false *unless* ColVar=@var, thus applying the filter. I was confused the first time I read this filter style but use it everywhere now.
Michael Haren
mancmanomyst.myopenid.com: that's what this does. I suggest making a truth table--it's not intuitive at first.
Michael Haren
thank you - I understand now. That really helps!
mancmanomyst
Be aware that although this technique is commonly used, it can have pitfalls especially when there are a large number of optional parameters. Do a SO search for 'parameter sniffing' and 'cached query plans'
Mitch Wheat
@Mich, good point. I actually came around some timing problems when using the technique specified in the answer as the execution plans were not optimal for some parameter sets. Using "WITH RECOMPILE" option for stored procedure somehow improved performance
kristof
A: 

You can also use IsNull or Coalesce function

Where accomodationTypeFK = IsNull(@accomodationFK, accomodationTypeFK)
    And regionFK = Coalesce(@regionFK,regionFK)
    And sleeps = IsNull(@sleeps,sleeps ) 
    And price Between IsNull(@priceFloor, Price) And IsNull(priceCeil, Price)

This does the same thing as Michael's suggestion above...

IsNull(), and Coalesce() work more or less the same way, they return the first non-Null argument in the list, except iSNull only allows 2 arguments, and Coalesce can take any number...

http://blogs.msdn.com/sqltips/archive/2008/06/26/differences-between-isnull-and-coalesce.aspx

Charles Bretana
Does this break the optimizer or is it smart enough to do this well? I get nervous when I see fall-through comparisons like that. Good mention, though. I use those functions a lot. Another lesser known function is NULLIF(a,b) which returns null if a and b are equal.
Michael Haren
Not sure about optimizer.. rely on the fact that the column is alone on one side of the = operator, so it 'should' be SARG-able. And NullIf() is kewl too, but I don't find as much of opportunity to use it...
Charles Bretana
Ditto: Be aware that although this technique is commonly used, it can have pitfalls especially when there are a large number of optional parameters. Do a SO search for 'parameter sniffing' and 'cached query plans'
Mitch Wheat
A: 

We've used a lot of COALESCE here in the past for "dynamic WHERE clauses" like you're talking about.

SELECT *
FROM  vehicles
WHERE ([vin]   LIKE COALESCE(@vin, [vin])     + '%' ESCAPE '\')
  AND ([year]  LIKE COALESCE(@year, [year])   + '%' ESCAPE '\')
  AND ([make]  LIKE COALESCE(@make, [make])   + '%' ESCAPE '\')
  AND ([model] LIKE COALESCE(@model, [model]) + '%' ESCAPE '\')

A big problem arises though when you want to optionally filter for a column that is also nullable... if the data in the column is null for a given row AND the user didn't enter anything to search by for that column (so the user input is also null), then that row won't even show up in the results (which, if your filters are optional, is incorrect exclusionary behavior).

In order to compensate for nullable fields, you end up having to do messier looking SQL like so:

SELECT *
FROM  vehicles
WHERE (([vin]   LIKE COALESCE(@vin, [vin])     + '%' ESCAPE '\')
       OR (@vin IS NULL AND [vin] IS NULL))
  AND (([year]  LIKE COALESCE(@year, [year])   + '%' ESCAPE '\')
       OR (@year IS NULL AND [year] IS NULL))
  AND (([make]  LIKE COALESCE(@make, [make])   + '%' ESCAPE '\')
       OR (@make IS NULL AND [make] IS NULL))
  AND (([model] LIKE COALESCE(@model, [model]) + '%' ESCAPE '\')
       OR (@model IS NULL AND [model] IS NULL))
Yadyn
As I recall, That is another difference between IsNull and Coalesce... One of them (I forget which) exhibits this behavior, and the other does not...
Charles Bretana
Now I have to look it up...
Charles Bretana
IsNull works ok here. Coalesce exhibits this issue. But you can also solve it with a cast... http://blogs.msdn.com/sqltips/archive/2008/06/26/differences-between-isnull-and-coalesce.aspx
Charles Bretana
A: 

Just so you understand, IF is procedural code in T-SQl. It canot be used in an insert/update/delete/select statement it can only be used to determine which of two statements you want to run. When you need different possibilities within a statement, you can do as above or use a CASE statement.

HLGEM
A: 

Try putting your IF statement around the entire SQL statement. That means will have one SQL statement for each condition. That worked for me.

Rich
this could lead to a hell of a lot of duplicated code.
Duncan