在SQL中处理'可选'where子句过滤器的正确方法?

时间:2009-11-10 04:31:29

标签: sql optimization

假设您有一个存储过程,它需要一个可选参数。您希望在SQL查询中使用此可选参数。通常这就是我看到它的完成方式:

SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = 'test'
AND (@MyOptionalParam IS NULL OR t1.MyField = @MyOptionalParam)

这似乎运行良好,但如果使用STATISTICS IO ON运行查询,则会导致大量逻辑读取。我也尝试了以下变体:

SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = 'test'
AND t1.MyField = CASE WHEN @MyOptionalParam IS NULL THEN t1.MyField ELSE @MyOptionalParam END

它产生相同数量的高读数。如果我们将SQL转换为字符串,然后在其上调用sp_ExecuteSQL,则读取几乎为零:

DECLARE @sql nvarchar(max)

SELECT @sql = 'SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = ''test'''

IF @MyOptionalParam IS NOT NULL
BEGIN
     SELECT @sql = @sql + ' AND t1.MyField = @MyOptionalParam '
END

EXECUTE sp_ExecuteSQL @sql, N'@MyOptionalParam', @MyOptionalParam
我疯了吗?为什么选择哪些条款如此难以正确?

更新:我基本上都在询问是否有办法将标准语法保留在存储过程中并获得低逻辑读取,就像sp_ExecuteSql方法一样。构建字符串对我来说似乎完全疯狂......更不用说它使维护,调试,可视化变得更加困难......

5 个答案:

答案 0 :(得分:4)

  

如果我们将SQL转换为字符串,然后在其上调用sp_ExecuteSQL,读取几乎为零......

  1. 因为您的查询不再评估OR,因为您可以看到杀死sargability
  2. 使用sp_executesql时缓存查询计划; SQL Server不必进行硬解析......
  3. 优秀资源:The Curse & Blessing of Dynamic SQL

    只要您使用参数化查询,就应该免于SQL Injection attacks

答案 1 :(得分:2)

这是可选参数技术的另一种变体:

SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = 'test'
AND t1.MyField = COALESCE(@MyOptionalParam, t1.MyField)

我很确定它会遇到相同的性能问题。如果性能为#1那么你可能会陷入分叉逻辑和接近重复的查询或构建字符串,这在TSQL中同样痛苦。

答案 2 :(得分:1)

您在前两个SQL语句中使用“OR”子句(隐式和显式)。最后一个是“AND”标准。 “OR”总是比“AND”标准更昂贵。不,你不是疯了,应该是预期的。

答案 3 :(得分:0)

编辑:添加link to similar question/answer with context as to why the union / if...else approach works better than OR logic(FYI,Remus,此链接中的回答者,曾用于开发服务代理和其他技术的SQL Server团队)

从使用“或”语法更改为联合方法,您将看到2次搜索应该使您的逻辑读取数尽可能低:

SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = 'test'
AND @MyOptionalParam IS NULL 
union all
SELECT * FROM dbo.MyTableName t1
WHERE t1.ThisField = 'test'
AND t1.MyField = @MyOptionalParam

如果要删除结果,请使用“union”而不是“union all”。

编辑:演示显示优化器足够智能,可以在UNION中使用空变量值排除扫描:

if object_id('tempdb..#data') > 0
    drop table #data
go

-- Put in some data
select  top 1000000
        cast(a.name as varchar(100)) as thisField, cast(newid() as varchar(50)) as myField
into    #data
from    sys.columns a
cross join sys.columns b
cross join sys.columns c;
go

-- Shwo count
select count(*) from #data;
go

-- Index on thisField
create clustered index ixc__blah__temp on #data (thisField);
go

set statistics io on;
go

-- Query with a null parameter value
declare @MyOptionalParam varchar(50);
select  *
from    #data d 
where   d.thisField = 'test'
and     @MyOptionalParam is null;
go

-- Union query
declare @MyOptionalParam varchar(50);
select  *
from    #data d 
where   d.thisField = 'test'
and     @MyOptionalParam is null
union all
select  *
from    #data d 
where   d.thisField = 'test'
and     d.myField = '5D25E9F8-EA23-47EE-A954-9D290908EE3E';
go

-- Union query with value
declare @MyOptionalParam varchar(50);
select @MyOptionalParam = '5D25E9F8-EA23-47EE-A954-9D290908EE3E'
select  *
from    #data d 
where   d.thisField = 'test'
and     @MyOptionalParam is null
union all
select  *
from    #data d 
where   d.thisField = 'test'
and     d.myField = '5D25E9F8-EA23-47EE-A954-9D290908EE3E';
go

if object_id('tempdb..#data') > 0
    drop table #data
go

答案 4 :(得分:-1)

从使用“或”语法更改为双查询方法,您将看到2个不同的计划,可以使您的逻辑读取数尽可能低:

IF @MyOptionalParam is null
BEGIN

  SELECT *
  FROM dbo.MyTableName t1

END
ELSE
BEGIN

  SELECT *
  FROM dbo.MyTableName t1
  WHERE t1.MyField = @MyOptionalParam

END

你需要在这里减少程序员减少重复的冲动。意识到你要求两个根本不同的执行计划,并且需要两个查询才能产生两个计划。

相关问题