我正在处理带有几个可选参数的存储过程。其中一些参数是单个值,并且很容易使用WHERE子句,如:
WHERE (@parameter IS NULL OR column = @parameter)
但是,在某些情况下,WHERE条件更复杂:
WHERE (@NewGroupId IS NULL OR si.SiteId IN (SELECT gs.SiteId
FROM [UtilityWeb].[dbo].[GroupSites] AS gs
WHERE gs.GroupId = @NewGroupId))
当我取消注释这些复杂的WHERE子句时,查询执行时间加倍,执行计划变得非常复杂。虽然执行计划不会打扰我,但是查询的执行时间加倍是一个明确的问题。
是否有其他人在其存储过程中使用可选参数的最佳实践或模式?
这是动态SQL是更好的解决方案吗?
答案 0 :(得分:5)
我会为可用或不可用的参数创建单独的查询。
这将创建更简单的SQL,优化器将做得更好。
像这样:
if (@parameter IS NULL) then begin
select * from foo
end
else begin
select * from foo where value = @parameter
end
在你需要重新设计许多参数时,你需要动态的sql解决方案,然后也总是使用参数,你可能会被SQL-Injection错误所困扰。
组合也是可能的。最可能使用的查询/查询,您可以完整编码,并获得预编译。所有其他组合都是动态创建的。
答案 1 :(得分:4)
主要问题可能是parameter sniffing,并且根据您的哪些参数为NULL,优化执行计划截然不同。尝试使用RECOMPILE运行存储过程。
与某些观点相反,Sql Server does do短路评估 - 尽管(与所有查询优化一样)可能不是您想要的。
BTW - 我可能会将查询的那部分重写为JOINed派生表:
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
它可能会影响查询计划,也可能不会影响查询计划,但它对我来说有点清晰。
答案 2 :(得分:4)
CASE陈述是你的朋友......
而不是:
if (@parameter IS NULL) then begin
select * from foo
end
else begin
select * from foo where value = @parameter
end
您可以使用:
SELECT * FROM foo
WHERE value = CASE WHEN @parameter IS NULL THEN value ELSE @parameter END
或者
SELECT * FROM foo
WHERE value = ISNULL(@parameter,value)
我更倾向于使用CASE语句,因为我的可选参数可能使用某些值而不是NULL ...
答案 3 :(得分:2)
在这种情况下,动态SQL可能是更好的解决方案,特别是如果存储过程仅包装此一个查询。
要记住的一件事是SQL Server不会在单个查询中短路布尔表达式。在许多语言中,如果a为真,“(a)||(b)”将不会导致b被评估。同样,如果a为假,则“(a)&&(b)”不会导致b被评估。在SQL Server中,情况并非如此。因此,在您给出的示例中,即使@NewGroupId不为null,也会评估“或”后端的查询。
答案 4 :(得分:2)
对于少量可选参数,从GvS建议的几个静态查询中选择一个条件是可以的。
但是,如果存在多个参数,则会变得难以处理,因为您需要处理所有排列 - 使用5个参数即32个静态查询!使用动态SQL,您可以构造最适合给定参数的精确查询。一定要使用绑定变量!
答案 5 :(得分:1)
恕我直言,参数嗅探问题可以通过将所有参数复制到变量中来解决;然后避免不惜一切代价直接使用参数,而是使用变量。例如:
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