我知道有很多关于这个话题的问题,即使是我刚才问过的一个问题(here)。现在我遇到了一个不同的问题,我和我的同事都不知道这种奇怪行为的原因是什么。
我们有一个相对简单的SQL语句:
SELECT
CONVERT(DATETIME, SUBSTRING(MyText, CHARINDEX('Date:', MyText) + 8, 16) AS MyDate,
SomeOtherColumn,
...
FROM
MyTable
INNER JOIN MyOtherTable
ON MyTable.ID = MyOtherTable.MyTableID
WHERE
MyTable.ID > SomeValue AND
MyText LIKE 'Date: %'
这不是我的数据库,也不是我的SQL语句,并且我没有创建用于在varchar列中存储日期时间值的优秀模式,所以请忽略该位。
我们现在面临的问题是SQL转换错误241(“从字符串转换日期和/或时间时转换失败。”)。
现在我知道查询优化器可能会改变执行计划,在尝试转换后可以使用WHERE子句来过滤结果,但真正奇怪的是,当我删除所有的时,我没有收到任何错误WHERE子句。
当我在上面的语句中添加一行时,我也没有收到任何错误,如下所示:
SELECT
MyText, -- This is the added line
CONVERT(DATETIME, SUBSTRING(MyText, CHARINDEX('Date:', MyText) + 8, 16) AS MyDate,
...
我一删除它就会再次出现转换错误。手动检查MyText列中的值而不尝试转换它们并不表示存在任何可能导致问题的记录。
转换错误的原因是什么?当我也选择列作为SELECT语句的一部分时,为什么我不碰到它?
更新
这是执行计划,虽然我不认为它会有所帮助。
答案 0 :(得分:1)
有时,SQL Server通过在流程早期推送转换操作来积极优化,而不是原本需要的。 (不应该。请参阅Connect上的SQL Server should not raise illogical errors作为示例。)
当您选择:
时CONVERT(DATETIME, SUBSTRING(MyText, CHARINDEX('Date:', MyText) + 8, 16)
然后优化器决定它可以执行此转换作为表/索引扫描的部分或搜索 - 就在它从表中读取数据的那一点(重要的是,之前,或者同时,作为WHERE
子句过滤器)。然后,查询的其余部分可以使用转换后的值。
选择:
MyText, -- This is the added line
CONVERT(DATETIME, SUBSTRING(MyText, CHARINDEX('Date:', MyText) + 8, 16)
它决定让转换稍后发生。重要的是,现在(通过偶然事件)转换发生的时间晚于WHERE
子句过滤器,该过滤器应该通过权限在尝试转换之前过滤所有行。
处理此问题的唯一安全方法是在尝试转换之前强制进行过滤。如果您不处理聚合,CASE
表达式可能足够安全:
SELECT CASE WHEN MyText LIKE 'Date: %' THEN CONVERT(DATETIME, SUBSTRING(MyText, CHARINDEX('Date:', MyText) + 8, 16) END
否则,更安全的选项是将查询拆分为两个单独的查询,并将中间结果存储在临时表或表变量中(视图,CTE和子查询不计算,因为优化器可以“透视”这样的结构)