避免where子句中的非sargable参数的解决方案

时间:2013-03-30 20:31:02

标签: sql sql-server sql-server-2008 tsql

在此查询的code_list CTE中,我有一个行构造函数,最终将获取任意数量的参数。 icd CTE中的列patient_codes是一个五位数的标识符,最能描述行构造函数具有的三位数代码。表icd_patient有1亿行,所以为了性能,我想在进行任何进一步的工作之前在这个表上记录行。我有

;with code_list(code_list)
as
(
    select  x.code_list
      from (values ('70700'),('25002')) as x(code_list)
),patient_codes
as
(

select distinct  icd,pat_id,id
    from icd_patient
    where icd in (select icd from code_list)
)
select distinct pat_id from patient_codes

但问题是,在icd_patient表中,所有icd列都是五位数且更具描述性。如果我查看此查询的执行计划,它会非常精简。如果我做

;with code_list(code_list)
as
(
    select  x.code_list
      from (values ('70700'),('25002')) as x(code_list)
),patient_codes
as
(
    select substring(icd,1,3) as icd,pat_id
      from icd_patient2
      where substring(icd,1,3) in (select * from code_list)
)
select * from patient_codes

如果由于where子句中的子字符串表达式,课程会对性能产生很大影响。是否存在类似于like in的内容,以便我可以利用我的索引?

icd_patient上的索引 CREATE NONCLUSTERED INDEX [ix_icd_patient] ON [dbo].[icd_patient2] ( [pat_id] ASC ) INCLUDE ( [id],

3 个答案:

答案 0 :(得分:5)

这个更简单的查询应该比现有查询更好(或者,最坏,相同)。

select pat_id
    FROM dbo.icd_patient
    where icd LIKE '707%'
       OR icd LIKE '250%'
GROUP BY pat_id;

请注意,只有在此列上实际存在索引时,sargability才会很重要。

另一种选择(因为OR有时可以使优化器适合):

SELECT pat_id FROM 
(
  SELECT pat_id
    FROM dbo.icd_patient
    WHERE icd LIKE '707%'
  UNION ALL
  SELECT pat_id
    FROM dbo.icd_patient
    WHERE icd LIKE '250%'
) AS x
GROUP BY pat_id;

为了使这个可扩展性超出少数OR条件,我会使用表值参数(TVP)。

CREATE TYPE dbo.StringPatterns AS TABLE(s VARCHAR(3) PRIMARY KEY);

然后您的存储过程可以说:

CREATE PROCEDURE dbo.whatever
  @sp dbo.StringPatterns READONLY
AS
BEGIN
  SET NOCOUNT ON;

  SELECT p.pat_id
    FROM dbo.icd_patient AS p
    INNER JOIN @sp AS sp
    ON p.pat_id LIKE sp.s + '%'
  GROUP BY p.pat_id;
END

然后,您可以从DataTable或C#中的其他集合传入一组三字符子串。以T-SQL为例:

DECLARE @p dbo.StringPatterns;
INSERT @p VALUES('707'),('250');
EXEC dbo.whatever @sp = @p;

答案 1 :(得分:2)

like in之类的东西不存在。以下是可以理解的:

select *
from icd_patient
where icd like '70700%' or
      icd like '25002%'

因为具有常量初始子字符串的like是SQL Server的特例。当右边的字符串是变量时,这不起作用。

一种解决方案是在icd_patient表上创建一个索引视图,其中包含icd代码前五个字符的索引。

答案 2 :(得分:2)

使用“IN”使得两侧的命令部分都不可搜索。讨论结束。

说他使用子字符串修复它,完全改变它返回的内容,而它仍然没有被攻击。

任何“修复”都应与结果完全匹配。实际修复是加入cte所以五个字符匹配或在cte中放入三个字符并在连接中匹配或在cte中放入4个字符,其中第四个是“%”并使用LIKE连接匹配

使用以“%”开头的“like”会增加搜索的复杂性,但它仍然会使用索引来查找值,因为解析索引应该使用较少的读数,只需在搜索时获取完整的表行是成功的。