从varchar转换为datetime导致值超出范围

时间:2018-12-30 04:59:34

标签: sql sql-server sql-server-2008

注意:这不是一个简单的问题;视图中的转换使操作变得很复杂。

在查询几层视图的select语句上出现以下错误。

  

将varchar数据类型转换为datetime数据类型   值超出范围。

通常,这将是一个非常简单的问题,可以通过查找错误数据并将其修复,或者在选择中添加一些其他逻辑来解决。那没有用。

通过侦查,我确定这是导致错误的代码:

  ,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里面有垃圾,所以我添加了一个检查,确认它是一个日期,当日期不包含日期时,该日期可能会阻止以后的转换运行:

  ,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始终包含一个日期,当状态在指定范围内时,该日期可以转换为日期时间。根据设计,它会包含状态以外的日期以外的日期。我认为问题在于,正在该varchar上尝试进行转换,而没有首先检查hm.Status的保护。这似乎违反了运营商的优先顺序。

[EDIT]使用@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

2 个答案:

答案 0 :(得分:2)

您可以使用前面的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,则始终存在潜在的问题。将该列作为真正的时态数据类型会更好。

还要注意,这是一个“隐式转换”,因为一个字符串被强制放入datetime中以便与dateadd()函数进行比较。我认为应避免隐式转换。

答案 1 :(得分:0)

WHERE中,引擎可以并且将通过{想要的谓词“(如果在优化方面看起来更有希望),将AND谓词通勤。但是我也不知道CASE是否也适用。

但是执行顺序并不一定是造成这种情况的原因。

摘自手册ISDATE (Transact-SQL)

  

请注意,日期时间数据的范围是1753-01-01至9999-12-31,而日期数据的范围是0001-01-01至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())

当然最好是为该列更正数据和正确的数据类型。