我有一个查询,其中包含十几个可选参数,我称之为:
select *
from table
where (@param1 is null or field1 = @param1) and
(@param2 is null or field2 = @param2) and
(@param3 is null or field3 = @param3) and
(@param4 is null or field4 = @param4) and
(@param5 is null or field5 = @param5)
它工作得非常好,但其性能非常糟糕。只需几分钟即可完成,而同一查询仅使用每次所需的参数在几秒钟内运行。
例如,这会在几秒钟内对同一数据运行:
select *
from table
where field2 = @param2 and
field4 = @param4 and
field5 = @param5
是否有可能优化第一个查询,以便使用与传递的参数对应的索引?现在我被迫从我的应用程序动态构建SQL语句,因此它只包含所需的参数(第二个示例),代码需要更多时间,并且更容易引入您在运行时仅在运行时检测到的错误参数。
谢谢。
答案 0 :(得分:1)
“是否有可能优化第一个查询,以便使用与传递的参数对应的索引?”
没有
你犯了一个常见的错误,试图将多个不同的查询类型统一为一个,这是为了避免编写大量SQL的错误目的/目标。
单个查询意味着只有一个访问数据的单一访问路径,并且该单个访问路径将用于查询的每个实例(例如,无论是否真的使用了param1)
查询应该以这样的方式编写,即他们仍然允许 DBMS确定数据的访问路径。在编译查询时而不是在执行查询时完成此确定。 (这种区别因动态SQL等设施而有些模糊,但这并不意味着它已完全消失。)这意味着查询优化器无法考虑参数值是什么,对于查询的特定调用,在确定访问路径时。
您主动编写查询的方式剥夺了DBMS确定良好访问路径的可能性。而且你会得到你应得的表现。
解决方案正如您已经观察到的那样:根据实际使用的选择标准动态生成SQL(并处理所有可能的注入问题),或者(甚至更好,性能方面)提供有限的一组预定义的查询可能性。
答案 1 :(得分:1)
如果索引正确,这将允许索引搜索而不是扫描,因为它包含每列的过滤器:
select *
from testtable
where (field1 = @param1) and
(isnull(field2, '_Dummy') = iif(@param2 is null, isnull(field2, '_Dummy'), @param2)) and
(isnull(field3, '_Dummy') = iif(@param3 is null, isnull(field3, '_Dummy'), @param3)) and
(isnull(field4, '_Dummy') = iif(@param4 is null, isnull(field4, '_Dummy'), @param4)) and
(isnull(field5, '_Dummy') = iif(@param5 is null, isnull(field5, '_Dummy'), @param5))
提供的早期代码不允许使用空列值:
select *
from table
where (field1 = @param1) and
(field2 = iif(@param2 is null, field2, @param2)) and
(field3 = iif(@param3 is null, field3, @param3)) and
(field4 = iif(@param4 is null, field4, @param4)) and
(field5 = iif(@param5 is null, field5, @param5))
答案 2 :(得分:1)
在@Damien_The_Unbeliever链接的文章之后,我将添加Query Hint OPTION(RECOMPILE),因此每次调用它时都会编译它,并且将使用与传递的参数值对应的索引进行优化。
select *
from table
where (@param1 is null or field1 = @param1) and
(@param2 is null or field2 = @param2) and
(@param3 is null or field3 = @param3) and
(@param4 is null or field4 = @param4) and
(@param5 is null or field5 = @param5)
OPTION (RECOMPILE)
但正如@Erwin_Smout所说,文章还指出,对于更复杂的查询,动态构建句子会更好。
谢谢大家。
答案 3 :(得分:1)
我通常会这样写你的查询。除非变量为null,否则它会将每列与其变量进行比较,在这种情况下,它会将列与自身进行比较。您可以添加重新编译提示,但我很少需要它。
select *
from table
where (field1 = isnull(@param1, field1) and
(field2 = isnull(@param2, field2) and
(field3 = isnull(@param3, field3) and
(field4 = isnull(@param4, field4) and
(field5 = isnull(@param5, field5)
option (recompile)
编辑: 当列可以包含null时,您可以使用isnull / coalesce编写查询以将null转换为值,例如空白或零,具体取决于您的数据类型。
select *
from table
where (isnull(field1, '') = coalesce(@param1, field1, '') and
(isnull(field2, '') = coalesce(@param2, field2, '') and
(isnull(field3, '') = coalesce(@param3, field3, '') and
(isnull(field4, '') = coalesce(@param4, field4, '') and
(isnull(field5, '') = coalesce(@param5, field5, '')
option (recompile)