Reputation: 10063
Note: this is not a simple problem; it is complicated by the conversion being in a view.
I am getting the following error on a select statement that is querying a couple layers of views.
The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.
Normally this would be a very simple problem to solve by finding the errant data and fixing it, or putting some additional logic in the select. That has not worked.
Thru some sleuthing I have determined that this is the code that is causing the error:
,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
So I suspected that StatusEff had garbage in it, so I add a check that it is a date that would presumably keep the later conversion from running when it did not contain a date:
,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
and I still get the error.
If I run the view by itself it runs fine.
I suspect that when the optimizer has to deal with a couple layers of code it screws up and attempts the conversion on hm.StatusEff without enforcing the evaluation order and protection of checking ISDATE()
.
This is an old version of sql server that we are working on updating; but I can't update now.
I am thinking there may be some way to use a CASE statement to more explicitly force the order of the evaluation.
Another thought is: Does SQL have the concept of evaluation order?
[EDIT] to clarify: the hm.StatusEff varchar always contains a date that can be converted to a datetime when the status is in the indicated range. Be design it will contain other than a date when status is outside of that range. I believe the problem is that the conversion is being attempted on that varchar without the protection of checking hm.Status first. This seems to violate the order of precedence for operators.
[EDIT] Using @Used_by_Already 's suggestion I did this and it worked:
,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
Upvotes: 0
Views: 1323
Reputation: 35573
You can dictate what happens if that column is not a date by using a preceding when
e.g.
,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
This should reduce the incidence of the conversion failure, but if that column is a varchar that attempts to store dates strings then there will always be potential issues. It would be much better to have that column as a true temporal data type.
Also note that this is an "implicit conversion" because a string is being forced into a datetime for the comparison to the dateadd() function. Implicit conversions should be avoided in my opinion.
Upvotes: 2
Reputation: 37472
In the WHERE
the engine can and will commute AND
ed predicates as it "wants" (if it looks more promising in regard to optimization). But I don't know if holds true for a CASE
too.
But order of execution is not necessarily the cause for this.
From the manual ISDATE (Transact-SQL):
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.
For example if you have a '0001-01-01' in there, isdate()
accepts it, it's a valid date. But the conversion to a datetime
will fail. So you should also check if hm.statuseff
is after or at 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())
Best of course would be to correct the data and a proper data type for that column.
Upvotes: 1