Gorgsenegger
Gorgsenegger

Reputation: 7856

Why do I get a DATETIME conversion error in TSQL?

I know there are numerous questions about this topic, even one I asked myself a while ago (here). Now I ran into a different problem, and neither myself nor my colleagues know what the reason for the strange behaviour is.

We've got a relatively simple SQL statement quite like this:

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: %'

This is not my database and also not my SQL statement, and I didn't create the great schema to store datetime values in varchar columns, so please ignore that bit.

The problem we are facing right now is a SQL conversion error 241 ("Conversion failed when converting date and/or time from character string.").

Now I know that the query optimiser may change the execution plan that the WHERE clause may be used to filter results after the conversion is attempted, but the really strange thing is that I don't get any errors when I delete all of the WHERE clause.

I also don't get any errors when I add a single line to the statement above as follows:

SELECT
    MyText, -- This is the added line
    CONVERT(DATETIME, SUBSTRING(MyText, CHARINDEX('Date:', MyText) + 8, 16) AS MyDate,
    ...

As soon as I remove it I get the conversion error again. Manually checking the values in the MyText column without trying to convert them does not show that there are any records which might cause a problem.

What is the reason for the conversion error? Why do I not run into it when I also select the column as part of the SELECT statement?

Update

Here the execution plan, although I don't think it's going to help. part1 enter image description here

Upvotes: 0

Views: 520

Answers (1)

Damien_The_Unbeliever
Damien_The_Unbeliever

Reputation: 239636

Sometimes, SQL Server aggressively optimizes by pushing conversion operations earlier in the process than they would otherwise need to be. (It shouldn't. See SQL Server should not raise illogical errors on Connect, as an example).

When you just select:

CONVERT(DATETIME, SUBSTRING(MyText, CHARINDEX('Date:', MyText) + 8, 16)

Then the optimizer decides it can perform this conversion as part of the table/index scan or seek - right at the point at which it's reading the data from the table (and, importantly, before, or at the same time, as the WHERE clause filter). The rest of the query can then just use the converted value.

When you select:

MyText, -- This is the added line
CONVERT(DATETIME, SUBSTRING(MyText, CHARINDEX('Date:', MyText) + 8, 16)

It decides to let the conversion happen later. Importantly, the conversion now (by happenstance) happens later than the WHERE clause filter which should, by rights, be filtering all rows before the conversion is attempted.


The only safe way to deal with this is to force the filtering to definitely occur before the conversion is attempted. If you're not dealing with aggregates, a CASE expression may be safe enough:

SELECT CASE WHEN MyText LIKE 'Date: %' THEN CONVERT(DATETIME, SUBSTRING(MyText, CHARINDEX('Date:', MyText) + 8, 16) END

Otherwise, the even safer option is to split the query into two separate queries, and store the intermediate results in a temp table or table variable (views, CTEs and subqueries don't count, because the optimizer can "see through" such constructs)

Upvotes: 1

Related Questions