SQL Server错误地重写我的查询?

时间:2014-07-01 13:44:28

标签: sql sql-server

输入中有脏数据。 我们正在尝试清理数据集,然后对清除的数据进行一些计算。

declare @t table (str varchar(10))
insert into @t select '12345' union all select 'ABCDE' union all select '111aa'

;with prep as
(
select *, cast(substring(str, 1, 3) as int) as str_int
from @t
where isnumeric(substring(str, 1, 3)) = 1
)

select * 
from prep
where 1=1
and case when str_int > 0 then 'Y' else 'N' end = 'Y'
--and str_int > 0

最后2行正在做同样的事情。第一个可以工作,但如果你取消注释第二个,它将与Conversion failed when converting the varchar value 'ABC' to data type int.

崩溃

显然,SQL Server正在重写将所有条件混合在一起的查询。 我猜它认为'case'是一个havy操作并将其作为最后一步执行。这就是为什么解决方案有效的原因。

这种行为是否以任何方式记录?还是一个bug?

1 个答案:

答案 0 :(得分:5)

这是SQL Server的一个已知问题,尽管用户这样做,但微软并不认为这是一个错误。两个查询之间的区别是执行路径。一个是在过滤之前进行转换,另一个是在之后。

SQL Server保留重新排序处理的权利。 documentation确实将子句的逻辑处理指定为:

  1. FROM
  2. ON
  3. JOIN
  4. WHERE
  5. GROUP BY
  6. WITH CUBE或WITH ROLLUP
  7. HAVING
  8. 选择
  9. DISTINCT
  10. ORDER BY
  11. TOP
  12. 使用(可能在此处未明确记录)CTE首先进行逻辑处理。 逻辑处理的意思是什么?嗯,这并不意味着捕获了运行时错误。它确实在编译阶段确定了标识符的范围。

    当SQL Server从数据源读取时,它可以添加新变量。这是一个方便的时间,因为一切都在内存中。但是,在过滤之前可能会发生,这是导致错误发生的原因。

    此问题的解决方法是使用case语句。因此,以下CTE通常会起作用:

    with prep as (
          select *, (case when isnumeric(substring(str, 1, 3)) = 1 and str not like '%.%'
                          then cast(substring(str, 1, 3) as int)
                     end) as str_int
          from @t
          where isnumeric(substring(str, 1, 3)) = 1
         )
    

    看起来很奇怪。而且我认为雷德蒙德也这么认为。 SQL Server 2012引入了try_convert()(请参阅here),如果转换失败,则返回NULL

    如果您可以指示SQL Server实现CTE,这也会有所帮助。这也可以解决这种情况下的问题。您可以投票选择向SQL Server here添加此类选项。