3

I am working on a stored procedure with several optional parameters. Some of these parameters are single values and it's easy enough to use a WHERE clause like:

WHERE (@parameter IS NULL OR column = @parameter)

However, in some instances, the WHERE condition is more complicated:

WHERE (@NewGroupId IS NULL OR si.SiteId IN (SELECT gs.SiteId
FROM [UtilityWeb].[dbo].[GroupSites] AS gs
WHERE gs.GroupId = @NewGroupId))

When I uncomment these complicated WHERE clauses, the query execution time doubles and the execution plan becomes remarkably more complicated. While the execution plan doesn't bother me, doubling the execution time of a query is a definite problem.

Is there a best practice or pattern that others have found for working with optional parameters in their stored procedures?

Is this one of those instances where dynamic SQL would be a better solution?

  • The execution plan should bother you, because it tells you exactly what the query executer is actually doing. – Jonas Lincoln Oct 23 '08 at 13:15
  • The complexity doesn't bother me outright because I have some very complex queries that run quickly. This one does not, sadly. –  Oct 23 '08 at 13:47
  • See also: http://stackoverflow.com/questions/532468/ignoring-a-null-parameter-in-t-sql/532510#532510 – Marc Gravell Feb 12 '09 at 15:06

6 Answers6

5

I would create separate queries for the parameter being available or not.

This will create simpler SQL, and the optimizer will do a better job.

Like this:

if (@parameter IS NULL) then begin
   select * from foo
end
else begin
   select * from foo where value = @parameter
end

In you have to many parameters to redesign like this, and you go for the dynamic sql solution, then also always use parameters, you might get bitten by the SQL-Injection bug.

A combination is also possible. The most likely used query/queries you code in full, and get precompiled. All other combinations are created dynamically.

Community
  • 1
  • 1
GvS
  • 52,015
  • 16
  • 101
  • 139
  • This is likely the best approach, yes, rather than dynamic SQL or using the ISNULL function, as ISNULL does not consider zero-length strings as null. This is particularly a problem depending upon how your procedure is called (from what application, etc.). Also, the execution is likely to be faster, using an if structure. Just of note: the if structure above should also test for datalength(@parameter) > 0, in order to avoid zero-length string issues. – David T. Macknet Sep 01 '10 at 09:42
4

The main problem is likely to be parameter sniffing, and wildly different optimal execution plans depending on which of your parameters are NULL. Try running the stored proc with RECOMPILE.

Contrary to some beliefs, Sql Server does do short circuit evaluations - though (as with all query optimizations) it may not be exactly what you wanted.

BTW - I would probably rewrite that portion of the query as a JOINed derived table:

SELECT * 
FROM Table as si
JOIN (
  SELECT SiteId
  FROM [UtilityWeb].[dbo].[GroupSites]
  WHERE GroupId = ISNULL(@NewGroupId, GroupId)
  /* --Or, if all SiteIds aren't in GroupSites, or GroupSites is unusually large 
     --this might work better
  SELECT @newGroupId
  UNION ALL
  SELECT SiteId FROM [UtilityWeb].[dbo].[GroupSites]
  WHERE GroupId = @NewGroupId
  */
) as gs ON
  si.SiteId = gs.SiteId

It may or may not influence the query plan, but it's a bit cleaner to me.

Mark Brackett
  • 84,552
  • 17
  • 108
  • 152
4

CASE statements are your friend...

Rather than:

if (@parameter IS NULL) then begin
   select * from foo
end
else begin
   select * from foo where value = @parameter
end

You can use:

SELECT * FROM foo 
WHERE value = CASE WHEN @parameter IS NULL THEN value ELSE @parameter END

Or

SELECT * FROM foo 
WHERE value = ISNULL(@parameter,value)

I tend to use CASE statements more because my optional parameters may use certain values instead of NULL's...

Kevin Fairchild
  • 10,891
  • 6
  • 33
  • 52
  • I would strongly caution against this approach. If you want to pull in all results when your parameter is null, but your field is nullable, then you will have a "WHERE NULL = NULL" situation and those always evaluate to false, thus you would be filtering out results where the field values contain null. Just bite the bullet and use parametrized dynamic SQL. – MikeTeeVee Nov 10 '11 at 17:33
  • @MikeTeeVee, good point. Our filters aren't used on nullable fields, though, so we don't worry about that. – Kevin Fairchild Dec 14 '11 at 17:58
2

Dynamic SQL is probably a better solution in this case, particularly if the stored procedure only wraps this one query.

One thing to keep in mind is that SQL Server doesn't do short circuiting of boolean expressions inside a single query. In many languages "(a) || (b)" will not cause b to be evaluated if a is true. Similarly, "(a) && (b)" will not cause b to be evaluated if a is false. In SQL Server, this is not the case. So in the example you give, the query on the back end of the "or" will get evaluated even if @NewGroupId is not null.

Curt Hagenlocher
  • 20,680
  • 8
  • 60
  • 50
2

For a small number of optional parameters, conditional choosing from one of several static queries as GvS suggests is OK.

However, this becomes unwieldy if there a several parameters, since you need to handle all permutations - with 5 parameters that is 32 static queries! Using dynamic SQL you can construct the exact query that best fits the parameters given. Be sure to use bind variables though!

Tony Andrews
  • 129,880
  • 21
  • 220
  • 259
1

IMHO, the parameter sniffing issue can be solved by copying all parameters into variables; then avoid using the parameters directly at all cost, use the variables instead. Example:


create proc ManyParams
(
    @pcol1 int,
    @pcol2 int,
    @pcol3 int
)
as
declare
    @col1 int,
    @col2 int,
    @col3 int

select
    @col1 = @pcol1,
    @col2 = @pcol2,
    @col3 = @pcol3

select 
    col1,
    col2,
    col3
from 
    tbl 
where 
    1 = case when @col1 is null then 1 else case when col1 = @col1 then 1 else 0 end end
and 1 = case when @col2 is null then 1 else case when col2 = @col2 then 1 else 0 end end
and 1 = case when @col3 is null then 1 else case when col3 = @col3 then 1 else 0 end end
Irawan Soetomo
  • 1,315
  • 14
  • 35
  • Yes, my WHERE clause can surely be simplified as: col1 = isnull(@col1, col1) and col2 = isnull(@col2, col2) and col3 = isnull(@col3, col3) – Irawan Soetomo Apr 28 '10 at 03:57
  • No, the safest way is: (@col1 is null or col1 = @col1) and (@col2 is null or col2 = @col2) and (@col3 is null or col3 = @col3) From 3415582/how-can-i-use-optional-parameters-in-a-t-sql-stored-procedure. – Irawan Soetomo May 14 '14 at 06:15