从 varchar 到 datetime 的转换导致值超出范围
Conversion from varchar to datetime resulted in out-of-range value
注意:这不是一个简单的问题;视图中的转换很复杂。
我在查询几层视图的 select 语句中收到以下错误。
The conversion of a varchar data type to a datetime data type resulted
in an out-of-range value.
通常这将是一个非常简单的问题,只需找到错误的数据并修复它,或者在 select 中添加一些额外的逻辑即可解决。那没有用。
通过一些调查,我确定这是导致错误的代码:
,case when ( (hm.Status >= 100 and hm.Status < 200)
or (hm.Status >= 300 and hm.Status < 400 and hm.StatusEff > dateadd(day, -30, GETDATE()))
)
and coalesce(mb.isMortBilled, 0) = 0
and hm.recurring = 1
then '1' else '0'
end as isRecurable
所以我怀疑 StatusEff 中有垃圾,所以我添加了一个检查,以确保它是一个日期,当它不包含日期时,可能会保留 运行ning 的后续转换:
,case when ( (hm.Status >= 100 and hm.Status < 200)
or (hm.Status >= 300 and hm.Status < 400 and ISDATE(hm.Statuseff) = 1 and convert(datetime, hm.StatusEff) > dateadd(day, -30, GETDATE()))
)
and coalesce(mb.isMortBilled, 0) = 0
and hm.recurring = 1
then '1' else '0'
end as isRecurable
我仍然得到错误。
如果我 运行 视图本身 运行 没问题。
我怀疑当优化器必须处理几层代码时,它搞砸了并尝试在 hm.StatusEff 上进行转换,而不强制执行评估顺序和检查 ISDATE()
的保护。
这是我们正在努力更新的 sql 服务器的旧版本;但是现在更新不了
我想可能有一些方法可以使用 CASE 语句来更明确地强制计算顺序。
另外一个想法是:SQL有求值顺序的概念吗?
[编辑] 澄清:hm.StatusEff varchar 始终包含一个日期,当状态在指定范围内时,该日期可以转换为日期时间。根据设计,当状态超出该范围时,它将包含除日期以外的日期。我认为问题在于,在没有首先检查 hm.Status 的保护的情况下,正在尝试对该 varchar 进行转换。这似乎违反了运算符的优先顺序。
[编辑] 使用@Used_by_Already 的建议我这样做了并且有效:
,case when hm.Status >= 100
and hm.Status < 200
and coalesce(mb.isMortBilled, 0) = 0
and hm.recurring = 1
then '1'
when ISDATE(hm.Statuseff) = 0 -- force this to happen before convert(datetime) below.
then '0'
when hm.Status >= 300 and hm.Status < 400 and convert(datetime, hm.StatusEff) > dateadd(day, -30, GETDATE())
and coalesce(mb.isMortBilled, 0) = 0
and hm.recurring = 1
then '1'
else '0'
end as isRecurable
您可以通过使用前面的 when
例如
来指定如果该列不是日期会发生什么
,case when ISDATE(hm.StatusEff) = 0 then '0'
when ( (hm.Status >= 100 and hm.Status < 200)
or (hm.Status >= 300 and hm.Status < 400 and convert(datetime, hm.StatusEff) > dateadd(day, -30, GETDATE()))
)
and coalesce(mb.isMortBilled, 0) = 0
and hm.recurring = 1
then '1' else '0'
end as isRecurable
这应该可以减少转换失败的发生率,但是如果该列是尝试存储日期字符串的 varchar,那么总会有潜在的问题。将该列作为真正的时间数据类型会好得多。
另请注意,这是一个 "implicit conversion",因为字符串被强制放入日期时间以与 dateadd() 函数进行比较。我认为应该避免隐式转换。
在 WHERE
中,引擎可以并且将会交换 AND
ed 谓词,因为它 "wants"(如果它在优化方面看起来更有希望)。但我不知道 CASE
是否也适用。
但执行顺序不一定是造成这种情况的原因。
Note that the range for datetime data is 1753-01-01 through 9999-12-31, while the range for date data is 0001-01-01 through 9999-12-31.
例如,如果您在其中输入“0001-01-01”,isdate()
会接受它,这是一个有效日期。但是转换为 datetime
会失败。所以你还应该检查 hm.statuseff
是在 1753-01-01 之后还是在 1753-01-01.
isdate(hm.statuseff) = 1
AND convert(date, hm.statuseff) >= convert(date, '1753-01-01')
AND convert(datetime, hm.statuseff) > dateadd(day, -30, getdate())
当然最好是更正该列的数据和正确的数据类型。
注意:这不是一个简单的问题;视图中的转换很复杂。
我在查询几层视图的 select 语句中收到以下错误。
The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.
通常这将是一个非常简单的问题,只需找到错误的数据并修复它,或者在 select 中添加一些额外的逻辑即可解决。那没有用。
通过一些调查,我确定这是导致错误的代码:
,case when ( (hm.Status >= 100 and hm.Status < 200)
or (hm.Status >= 300 and hm.Status < 400 and hm.StatusEff > dateadd(day, -30, GETDATE()))
)
and coalesce(mb.isMortBilled, 0) = 0
and hm.recurring = 1
then '1' else '0'
end as isRecurable
所以我怀疑 StatusEff 中有垃圾,所以我添加了一个检查,以确保它是一个日期,当它不包含日期时,可能会保留 运行ning 的后续转换:
,case when ( (hm.Status >= 100 and hm.Status < 200)
or (hm.Status >= 300 and hm.Status < 400 and ISDATE(hm.Statuseff) = 1 and convert(datetime, hm.StatusEff) > dateadd(day, -30, GETDATE()))
)
and coalesce(mb.isMortBilled, 0) = 0
and hm.recurring = 1
then '1' else '0'
end as isRecurable
我仍然得到错误。
如果我 运行 视图本身 运行 没问题。
我怀疑当优化器必须处理几层代码时,它搞砸了并尝试在 hm.StatusEff 上进行转换,而不强制执行评估顺序和检查 ISDATE()
的保护。
这是我们正在努力更新的 sql 服务器的旧版本;但是现在更新不了
我想可能有一些方法可以使用 CASE 语句来更明确地强制计算顺序。
另外一个想法是:SQL有求值顺序的概念吗?
[编辑] 澄清:hm.StatusEff varchar 始终包含一个日期,当状态在指定范围内时,该日期可以转换为日期时间。根据设计,当状态超出该范围时,它将包含除日期以外的日期。我认为问题在于,在没有首先检查 hm.Status 的保护的情况下,正在尝试对该 varchar 进行转换。这似乎违反了运算符的优先顺序。
[编辑] 使用@Used_by_Already 的建议我这样做了并且有效:
,case when hm.Status >= 100
and hm.Status < 200
and coalesce(mb.isMortBilled, 0) = 0
and hm.recurring = 1
then '1'
when ISDATE(hm.Statuseff) = 0 -- force this to happen before convert(datetime) below.
then '0'
when hm.Status >= 300 and hm.Status < 400 and convert(datetime, hm.StatusEff) > dateadd(day, -30, GETDATE())
and coalesce(mb.isMortBilled, 0) = 0
and hm.recurring = 1
then '1'
else '0'
end as isRecurable
您可以通过使用前面的 when
例如
,case when ISDATE(hm.StatusEff) = 0 then '0'
when ( (hm.Status >= 100 and hm.Status < 200)
or (hm.Status >= 300 and hm.Status < 400 and convert(datetime, hm.StatusEff) > dateadd(day, -30, GETDATE()))
)
and coalesce(mb.isMortBilled, 0) = 0
and hm.recurring = 1
then '1' else '0'
end as isRecurable
这应该可以减少转换失败的发生率,但是如果该列是尝试存储日期字符串的 varchar,那么总会有潜在的问题。将该列作为真正的时间数据类型会好得多。
另请注意,这是一个 "implicit conversion",因为字符串被强制放入日期时间以与 dateadd() 函数进行比较。我认为应该避免隐式转换。
在 WHERE
中,引擎可以并且将会交换 AND
ed 谓词,因为它 "wants"(如果它在优化方面看起来更有希望)。但我不知道 CASE
是否也适用。
但执行顺序不一定是造成这种情况的原因。
Note that the range for datetime data is 1753-01-01 through 9999-12-31, while the range for date data is 0001-01-01 through 9999-12-31.
例如,如果您在其中输入“0001-01-01”,isdate()
会接受它,这是一个有效日期。但是转换为 datetime
会失败。所以你还应该检查 hm.statuseff
是在 1753-01-01 之后还是在 1753-01-01.
isdate(hm.statuseff) = 1
AND convert(date, hm.statuseff) >= convert(date, '1753-01-01')
AND convert(datetime, hm.statuseff) > dateadd(day, -30, getdate())
当然最好是更正该列的数据和正确的数据类型。