Reputation: 20379
I created a table valued function, which takes an optional parameter, which should determine whether a rather lengthy calculation (PERCENTILE_CONT
in my case) should be run.
For this purpose I use a construct like this in my select statement
SELECT CASE @my_param
WHEN 1 THEN NULL
ELSE --run lengthy calc
END
However, looking at the execution plan it seems that the lengthy calculation is triggered irrespective of how I set the parameter.
The problem can be reproduced with the following lines:
CREATE TABLE #temp(a int)
INSERT INTO #temp Values(1)
DECLARE @i INT = 1;
SELECT CASE WHEN @i=1 THEN 1
ELSE PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY a) OVER ()
END
FROM #temp
If you look at the execution plan, you will see that the PERCENTILE_CONT
function is included irrespective of the value of @i
. Since this very function slows down my query by a bit, I was wondering how to best circumvent that?
I tried to add an IF
construct paired with an UPDATE
statement in my function and while this avoids running the PERCENTILE_CONT
function when not needed, it slows down the function when I want to include the PERCENTILE_CONT
function. So what other options do I have to run PERCENTILE_CONT
only in needed cases?
My median will eventually be calculated for several subgroups, thus I decided to go for PERCENTILE_CONT
in the first place, knowing that it is not the best performance wise, but is very readable. Hence, while I appreciate hints of how to calculate the median in a more efficient way, I would also be curious to learn how to avoid or short circuit the calculation without the need to fall back to an ordinary if
clause.
Upvotes: 0
Views: 208
Reputation: 280431
When SQL Server creates an execution plan for a query that uses an optional parameter, it has to generate a plan that is capable of handling both scenarios:
When a parameter is provided, it takes into account cardinality estimates based on that initial value (or lack of value), and then keeps that plan for subsequent executions (which is why performance can differ when the parameter values change but the plan remains the same).
This whole process gets exponentially more complex as you go from 1 to n optional parameters.
If you want to have one plan for the presence of a specific parameter, and another one for the absence, or even different plans compiled for different values of the param, you can use conditional logic like Gordon suggested or you can follow the kitchen sink approach.
For example:
SET @sql = N'SELECT ...';
SET @sql = @sql + CASE WHEN @my_param = 1 THEN N'1'
ELSE N'PERCENTILE_CONT(0.5) ... ' END;
...
EXEC sys.sp_executesql @sql;
I think this is a good balance between "one plan fits all" and "use OPTION (RECOMPILE)
to get a new plan every single time." You can even add OPTION (RECOMPILE)
to the dynamic SQL statement conditionally (for example you don't need it when the search ends up being a seek on a unique key, but it can make sense to have it when you might be searching for last name starts with S vs. last name contains Q).
Upvotes: 3
Reputation: 3744
You can use Option (Recompile) to get the desired execution plan.
Please see the result in the screenshot below:
Upvotes: 1