T-SQL 谜题:将其用作内联视图时查询失败

Posted

技术标签:

【中文标题】T-SQL 谜题:将其用作内联视图时查询失败【英文标题】:T-SQL puzzle: query fails when using it as an inline view 【发布时间】:2021-04-15 15:34:43 【问题描述】:

准备

create table t (x varchar(18))

insert into t (x) values ('8003372602728'), ('a')

查询 #1:

select cast(x as bigint) y 
from t 
where isnumeric(x) = 1 and cast(x as bigint) > 100

查询 #1 的结果:

y
--------------------
8003372602728

(1 row affected)

查询 #2:

select * 
from
    (select cast(x as bigint) y 
     from t 
     where isnumeric(x) = 1 and cast(x as bigint) > 100) a
where y is not null

查询 #2 的结果:

y
--------------------
8003372602728

消息 8114,第 16 级,状态 5,第 1 行 将数据类型 varchar 转换为 bigint 时出错。

查询#2的结果错误的原因是什么?

【问题讨论】:

SQL 不会按照您编写它们的顺序执行WHERE,它可以很容易地尝试在isnumeric(x)=1 之前传递cast(x as bigint)>100。无论如何,只需使用TRY_CONVERT/TRY_CAST,它的功能比ISNUMERIC 好得多。 尝试CAST 一个故意包含VARCHAR 值的列是一个坏主意。另外,我真的不明白where y is not null 子句的含义。但是,如果您坚持原样,您可以随时选择您想要CAST 的行到另一个表中,然后在那里进行您需要的任何操作。 @WAMLeslie 'where y is not null' 仅用于演示错误。如果我没有给出任何“where”条件,那么查询 #2 也可以正常工作!这就是谜题。 【参考方案1】:

首先,请注意这些查询之间的区别:

select cast(x as bigint) y 
from t 
where isnumeric(x) = 1
  and cast(x as bigint) > 100;

select cast(x as bigint) y 
from t 
where cast(x as bigint) > 100
  and isnumeric(x) = 1;

第一个有效,第二个因转换错误而失败。第一个有效,因为首先评估 isumeric 并且仅评估数值。第二个查询正在评估:CAST(x as bigint) first。理论上,这两个查询都不应该起作用,因为不能保证按任何特定顺序评估过滤器。

接下来,这可行:

select * 
from
    (select cast(x as bigint) y 
     from t 
     where isnumeric(x) = 1 and cast(x as bigint) > 100) a
----WHERE a.y > 100  

... 但是,一旦取消注释 WHERE 子句,它就会失败。这是因为外部查询的 WHERE 子句在确定 ISNUMERIC 是否为真之前评估 x。

这行得通:

select * 
from
    (select cast(x as bigint) y 
     from t 
     where isnumeric(x) = 1 and cast(x as bigint) > 100) a
    WHERE isnumeric(a.y) = 1

但是,再次,当我更改外部查询以包含 y > 100 的过滤器时...

select * 
from
    ( select cast(x as bigint) y 
      from t 
      where isnumeric(x) = 1 and cast(x as bigint) > 100) a
    WHERE isnumeric(a.y) = 1 and CAST(a.y AS bigint) > 100

失败了。为什么?因为无法保证评估顺序。

TRY_CAST 解决了两个问题:

首先,isnumeric 是一个专有的遗留函数,它不符合大多数人的想法。 isumeric 认为这一切都是数字:

SELECT ISNUMERIC('.'), ISNUMERIC('$'), ISNUMERIC('-'), ISNUMERIC('+'), ISNUMERIC('$+.');

第二,它解决了评估的顺序问题。此查询有效:

select * 
from
    (select TRY_CAST(x as bigint) y 
     from t 
     where TRY_CAST(x AS bigint) IS NOT NULL ) a
WHERE y > 100; -- Implies that it's NOT NULL

【讨论】:

+1 这是关键:“无法保证评估顺序”。由于看似任意的原因,优化器可以选择在isnumeric(x) = 1 之前评估cast(x as bigint),因此查询逻辑不应依赖于该顺序(而可以根据您的建议依赖TRY_CAST)。干得好。

以上是关于T-SQL 谜题:将其用作内联视图时查询失败的主要内容,如果未能解决你的问题,请参考以下文章

T-SQL谜题:将表的每一行作为内联函数的输入传递,并使用UNION ALL开发新的堆栈数据集

内联视图中的 ORA-1427

T-SQL Server 代理作业失败“用户无权执行此操作”

托斯卡谜题 73589 用 RBFW 解决。 (失败:SyntaxError:扫描字符串文字时 EOL (<string>)

内联T-SQL或子查询

散景内联嵌入,“加载资源失败”