2

考虑以下模式:CASE WHEN在SQL Server索引的使用:寻求VS扫描

create table dbo.SomeTable 
(
    ID int primary key identity(1, 1), 
    SomeColumn varchar(50) 
); 

create index IX_SomeColumn on dbo.SomeTable (SomeColumn); 

而且随着一些数据来填充它:

declare @i int = 1000; 

while @i > 0 begin 
    set @i = @i - 1; 

    insert dbo.SomeTable (SomeColumn) 
    values (convert(varchar, @i)) 
end 

此查询执行的索引查找:

这个查询执行索引扫描:
select ID 
from dbo.SomeTable 
where SomeColumn = '431' 
select ID 
from dbo.SomeTable 
where case when SomeColumn = '431' then 1 else 0 end = 1 

有没有办法让后者(或类似的东西)执行索引搜索呢?

我在问,因为我希望能够将case when放入视图的选择列表中,并在where子句中使用它,但如果我无法获得,它将永远不会与原始形式一样好SQL Server进行索引查找。

+2

你的第二个查询不SARGEBLE。除非将其应用于表中的每一行,否则无法知道“CASE”语句的值。这就是为什么你要扫描。当然,很显然,如果'SomeColumn ='431'''CASE'语句只会返回1,但是SQL Server看起来不够深入。 –

回答

3

如果你看两个查询的执行计划,seek谓词是非常不同的。

当你在where子句中没有使用CASE表达式时,seek谓词只留下Column(不必对列值进行任何计算),只需在索引上查找就可以找到=的另一端。

enter image description here

在另一方面,当你在where子句中使用CASE表达式,事情发生很大的变化,现在断言求参数已经在那里表达的两侧where子句。简单地说,SQL服务器不会在CASE WHEN [TEST_DB].[dbo].[SomeTable].[SomeColumn] = [@1] THEN [@2] ELSE [@3] END = CONVERT_IMPLICIT(int,[@4],0)之前产生什么值,直到它实际上已经被执行为止,因此使用可用索引选项离开窗口并且SQL服务器最终进行扫描。

enter image description here

使用CASE表达故事

避免道德where子句。

+1

通过添加两个文字常量的冗余比较,可以轻松防止自动参数化。优化器中没有任何逻辑来对这些表达式进行反向工程,并尝试使它们变得可运行。 –

+0

@MartinSmith哇这是很酷的把戏,只是测试它,是的,它确实删除了参数化,但很想知道为什么会出现这种行为? –

+0

因为如果它参数化可以防止其他优化的文字值。例如,如果你在'where 1 = 0'处编译时可以评估没有行将被返回并且完全避免访问表。如果1和0被参数替代,它不能这样做。 –

5

您可以通过使case when SomeColumn = '431' then 1 else 0 end表达式成为计算列并对计算列进行索引来获得搜索的唯一方法。

然后,您应该发现该表达式与允许查找的计算列匹配(以维护额外索引为代价)。

(如果遇到问题,支持自动paremeterisation防止比赛添加冗余1=1将防止这一点。SQL Fiddle with plan showing a seek

+0

真是个好戏! –