question

Diarmuid avatar image
Diarmuid asked

Optional Parameter or different stored procedure?

Hi I have a table Sales. Usually I will be adding to this table all data for a month. So:

CREATE Procedure AddMonth
@EnterMonth as int
AS
INSERT INTO X
SELECT FROM Y
WHERE Y.Month = @Month

Now sometimes I just want to only add Sales for a certain product. So two questions:

  1. Should I use a new stored procedure for sales by product and month? Thinking with regards to speed.
  2. Or, use an optional parameter of the Product. If such a thing is possible, of course.

Thanks

t-sqlstored-proceduresparameters
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Kev Riley avatar image
Kev Riley answered

You could add @Product as an optional parameter, but would have to be careful of parameter sniffing causing bad plans.

That would look like (assuming product is an int)

CREATE Procedure AddMonth
(
@EnterMonth as int,
@product int = null
)
AS
INSERT INTO X
SELECT FROM Y
WHERE Y.Month = @Month
and (@product is null or product = @product)

Make sure you check out the varied discussions on optional parameters.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Matt Whitfield avatar image
Matt Whitfield answered

+1 to kev - but I would write

ISNULL(@productID, productID) = productID

Instead of using an OR clause.

However it's not clear to me what you mean - because if you mean inserting all sales for a product in any month, then I woild split that out into a separate procedure...

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Grant Fritchey avatar image
Grant Fritchey answered

With such a generic question, it's hard to be specific, but in the interest of being contrarian...

No, don't use the same procedure with an optional parameter (which is possible, as Kev & Matt showed). Instead create a new procedure. The reason being, either Kev's or Matt's approach could lead to scans in the Product table and from a purely performance centered point of view, having a specified query that didn't include the ISNULL or the OR function would result in better index use and therefor more consistent and usually better performance.

1 comment
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Håkan Winther avatar image Håkan Winther commented ·
+1 I agree with you all, but from a performance perspective you should be careful about "optional" paramters as Grant answer. Even if you don't get scans you could end up with recompiles of the procedure and depending on complexity and usage of the procedure, this could lead to a bottleneck.
0 Likes 0 ·
TimothyAWiseman avatar image
TimothyAWiseman answered

I agree with Kev and Matt, but just for the sake of options remember you can do this:

CREATE Procedure AddMonth
(@EnterMonth as int,
@product int = null)
AS
if @product is null 
    INSERT INTO X
    SELECT *
    FROM Y
    WHERE Y.Month = @EnterMonth 
else
    INSERT INTO X
    SELECT *
    FROM Y
    WHERE Y.Month = @EnterMonth and 
    	product = @product
2 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Diarmuid avatar image Diarmuid commented ·
Thanks to everyone for their answers. Timoty, from the other answers, it seems like it would be more efficient to have a seperate procedure when I want all sales for a product in a month. Is your code a way round this, or would it still suffer from the recompile/preformance issues mentioned above? My main goal is consistent good performance from these procedures.
0 Likes 0 ·
TimothyAWiseman avatar image TimothyAWiseman commented ·
I believe that this and anything with optional parameters would have the potential to run into the recompiles mentioned by Hakan, but I have not done testing to confirm or deny this, so that is only an educated guess right now.
0 Likes 0 ·
Magnus Ahlkvist avatar image
Magnus Ahlkvist answered
**EDIT** I just had to check this out. With SQL Server 2005, doing a COUNT(*) on a table with some eight million rows, non clustered PK being an int identity column. I don't know how well it shows on this picture, but three queries, delivering the same result creates different plans, where the BETWEEN-approach "costs" 6% and the other two "costs" 46% and 47%. ![Screenshot from SSMS2005][1] **END EDIT** I saw this question now, very, very long after it has been answered, when Diarmuid got the Popular Question badge. I just wanted to add something that might or might not be valid anymore. When I was at a training course with MVP Tibor Karaszi, he told me that using BETWEEN is more efficient than using WHERE (productid=@productid OR @productid is null). That was when we started using SQL Server 2000 at work, having used SQL Server 7, so I don't know if this is still valid. CREATE Procedure AddMonth (@EnterMonth as int, @product int = null) AS INSERT INTO X SELECT * FROM Y WHERE Y.Month = @EnterMonth and product = BETWEEN COALESCE(@product, -2147483648) AND COALESCE(@product, 2147483648) Does anyone know if this is still valid? [1]: http://www.hejaenkoping.se/Query3.jpg
8 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

WilliamD avatar image WilliamD commented ·
Wouldn't that translate to the same thing behind the scenes once the optimiser has looked at it?
0 Likes 0 ·
Magnus Ahlkvist avatar image Magnus Ahlkvist commented ·
@WilliamD I'm looking in SQL Server 2005 and see that the plan differs with a slightly higher estimated CPU cost with the **WHERE product=@product or @product is null** approach compared to the BETWEEN approach. Estimated row count differs as well (with a factor 4). So the optimiser still treats the two differently. When comparing the approaches for a table with 10M+ rows, I get a "Paralellism (Gather Streams)" in the plan for the BETWEEN-approach, not for the other approach.
0 Likes 0 ·
WilliamD avatar image WilliamD commented ·
Also of note, is that the solution suggested by TimothyAWiseman would not be a cause of severe recompiles take a look at this from [Gail Shaw][1]. [1]: http://sqlinthewild.co.za/index.php/2010/12/14/do-if-statements-cause-recompiles/
0 Likes 0 ·
Magnus Ahlkvist avatar image Magnus Ahlkvist commented ·
I edited my answer with a screenshot from SSMS where I test three ways of doing the same thing.
0 Likes 0 ·
WilliamD avatar image WilliamD commented ·
Is that esimated or "real" costs? You mentioned that parallelism was there for BETWEEN and not the others, but the picture shows the reverse. I think that the cost may be incorrect if the values really are nulls. I would think the plan shown for the BETWEEN would lose out to an ordered index scan. The first and last plans are basically identical, and would be more consistant in execution time (if slightly slower for a non NULL @c value).
0 Likes 0 ·
Magnus Ahlkvist avatar image Magnus Ahlkvist commented ·
When I did a **SELECT \*** and looked at the estimated plan, that's where parallellism was in the BETWEEN approach. I didn't want to try **SELECT \*** with a big table, so I did a COUNT instead and the picture is from the real plan of those.
0 Likes 0 ·
Magnus Ahlkvist avatar image Magnus Ahlkvist commented ·
Now I have also done a **SELECT \*** with a specific value for @c, and those results are equally slow. Another comparison I did, which has more to do with the original question: SELECT \* FROM tbl_Statistic where statisticID=@c and SELECT \* FROM tbl_Statistic where statisticID=@c OR @c is null The first query does a Index Seek in the Non Clustered PK and a Key Lookup. It takes no time at all. The second query does a Clustered Index Scan to find this one row. So our bottom line is that the optimiser doesn't work very well with optional parameters...
0 Likes 0 ·
WilliamD avatar image WilliamD commented ·
In the second test from your last comment, the OR @c is null would have to be completely evaluated, hence the index scan. If you split the query like TimothyWiseman states, into two execution branches - deciding to either do WHERE statisticID=@C or just the entire table, you would have better performance when supplying a non null value for @c, a null value will always result in a table/index scan
0 Likes 0 ·

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.