为什么我在SQL中遇到DATETIME转换错误?

时间:2013-03-05 10:52:03

标签: tsql datetime type-conversion

我知道有很多关于这个话题的问题,即使是我刚才问过的一个问题(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语句的一部分时,为什么我不碰到它?

更新

这是执行计划,虽然我不认为它会有所帮助。 part1 enter image description here

1 个答案:

答案 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和子查询不计算,因为优化器可以“透视”这样的结构)