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

vav*_*vav 5 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
Run Code Online (Sandbox Code Playgroud)

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

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

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

Gor*_*off 5

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

SQL Server保留重新排序处理的权利.该文档不指定逻辑子句作为处理:

  1. 加入
  2. 哪里
  3. 通过...分组
  4. WITH CUBE或WITH ROLLUP
  5. HAVING
  6. 选择
  7. 不同
  8. 订购
  9. 最佳

(可能在此处未明确记录)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
     )
Run Code Online (Sandbox Code Playgroud)

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

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