SQL Server:函数优先级和where子句中的短路



考虑以下设置:

create table #test (val varchar(10))
insert into #test values ('20100101'), ('1')

现在,如果我运行这个查询

select * 
from #test
where ISDATE(val) = 1
and CAST(val as datetimeoffset) > '2005-03-01 00:00:00 +00:00'

会失败

从字符串转换日期和/或时间时转换失败

这告诉我where条件不是短路的,并且对两个函数都进行了评估。好的。

但是,如果我运行

select * 
from #test
where LEN(val) > 2
and CAST(val as datetimeoffset) > '2005-03-01 00:00:00 +00:00'

它不会失败,这告诉我where子句在这种情况下是短路的。

这个

select * 
from #test
where ISDATE(val) = 1
and CAST(val as datetimeoffset) > '2005-03-01 00:00:00 +00:00'
and LEN(val) > 2

再次失败,但如果我将长度检查移到cast之前,它就会工作。因此,看起来函数是按照它们在查询中出现的顺序进行计算的。

有人能解释第一次查询失败的原因吗?

它失败是因为SQL是声明性的,所以在生成计划时不会考虑条件的顺序(也不需要这样做)。

解决这个问题的通常方法是使用CASE,它对序列和何时停止有严格的规则。

在您的情况下,您可能需要嵌套的CASE,类似于以下内容:

WHERE
(
case when ISDATE(val) = 1 then 
case when CAST(val as datetimeoffset) > '2005-03-01 00:00:00 +00:00' and
LEN(val) > 2 
THEN 1 ELSE 0 END
ELSE 0
END
) = 1

(注意,这不太可能是真正正确的SQL,因为我刚刚输入了它)。

顺便说一句,即使你通过重新安排条件来"发挥作用",我也建议你不要这样做。接受SQL根本不能以这种方式工作的事实。随着数据的变化&统计数据更改,SQL升级,工作负载变化,添加索引,查询计划可能更改。任何"让它工作"的尝试充其量都是短暂的,所以使用CASE,一旦你做对了,它就会继续工作(前提是你在需要的地方嵌套CASE语句,并且在CASE条件下不会落入相同的优先级陷阱!)

如果您检查执行计划,这个谜团就会得到解答。CAST()LEN()都作为表扫描步骤的一部分应用,而IsDate()的测试是表扫描之后的单独Filter测试。

SQL引擎的内部优化似乎使用某些过滤功能作为数据检索的一部分,而其他功能则作为单独的过滤器,几乎可以肯定,这是一种查询优化形式,可以最大限度地减少从磁盘到主存的负载。然而,更复杂的函数,如IsDate(),在某些情况下取决于系统变量,如系统日期格式(是2017年1月2日还是2月1日?),需要在应用过滤器之前检索数据。

尽管我没有这方面的确切信息,但我强烈怀疑,任何比某个级别更耗费资源的过滤器都会被委派给查询计划中的"过滤器"步骤,并且在扫描/查找步骤中应用任何简单/快速到可以在读取数据时进行检查的过滤器。此外,如果可以对索引中的数据应用过滤器,我确信在测试任何非索引数据之前都会对其进行测试,只是为了最大限度地减少磁盘读取,这是糟糕的性能(这可能不适用于表的群集索引)。在这些情况下,短路可能并不简单,在对索引字段进行类似测试后,对非索引字段指定的IsDate()测试将被执行,无论它们在条件列表中的哪个位置。

也就是说,当条件在查询计划的同一步骤中执行时,它们似乎确实会短路。如果在临时表中插入类似"201612123"的字符串,然后在日期比较后添加对Len(val) < 9的检查,它仍然会生成错误,而不是在微小的优化中同时检查两个LEN()条件。

这告诉我,条件不短路,两个函数都被评估。

要进一步了解LoztInSpace的答案,您的术语表明您没有正确解释SQL。

SELECT语句的各个部分不是"函数"。整个语句是原子。您将查询作为单元提供,DBMS将作出响应。没有"之前"one_answers"之后"。只有查询

这些就是规则。制定查询的工作是提供一个有效的查询。这是一个合乎逻辑的过程:有效问题正确答案等。当你走出那一帧时,你可能会问,"为什么天空是七?"。

一个是对@LoztInSpace的回答的一个小澄清。当他提到你的陈述顺序时,他可能是在谈论你的查询的措辞,出于评估的目的,这是无关紧要的。顺序SQL语句按顺序执行,如图所示。SQL标准保证了这一点。

最新更新