Question

I have created a very simple example showing an issue I have encountered on a much more complicated query using dynamic SQL and I can't figure out why the CTE is causing the error when the standard version is not.

Error when using the CTE:

Conversion failed when converting date and/or time from character string.

I am trying to achieve the following with my dynamic query: Flag records that don't match a specified data type (date in this case). If they pass that step they are later checked against a look-up table to validate they are the values I was expecting (validate dates in a specified range).

 -- Table Setup
 CREATE TABLE #ValidDate 
 (
    V INT IDENTITY (1, 1) NOT NULL,
    VDate DATE NULL
 )

 INSERT INTO #ValidDate
 VALUES ('02/20/2014'), ('02/21/2014'), ('02/22/2014'), ('02/23/2014'), ('02/25/2014')

 CREATE TABLE #DatesToValidate 
 (
    I INT IDENTITY (1, 1) NOT NULL,
    IDate VARCHAR(30) NULL
 )

 INSERT INTO #DatesToValidate
 VALUES ('apple'), ('02/21/2014'), ('orange'), ('02/23/2014')

 CREATE TABLE #Errors
 (
    ID INT,
    Dates VARCHAR(30)
 )

 INSERT INTO #Errors
 SELECT *
 FROM #DatesToValidate
 WHERE ISDATE(IDate) <> 1

Below is the standard Query (Works):

 SELECT *
 FROM #DatesToValidate
 WHERE I NOT IN (   SELECT ID
                    FROM #Errors)
AND IDate NOT IN (  SELECT VDate
                    FROM #ValidDate)

Below is the CTE (Does not Work):

 ;WITH CTE AS
 (
    SELECT *
    FROM #DatesToValidate
    WHERE I NOT IN (    SELECT ID
                        FROM #Errors)
 )

 SELECT *
 FROM CTE
 WHERE IDate NOT IN (   SELECT VDate
                        FROM #ValidDate)
Était-ce utile?

La solution

This SQL Fiddle shows the problem. Neither version is guaranteed to work. If you swap the order of the conditions in the and then you'll get a conversion failure.

The reason the and version happens to work in this case is because of short-circuiting. When the first condition fails, the second doesn't get run. This is an optimization that SQL Server can take advantage of. And does in this case, so you think it works.

The CTE version doesn't work because there is no guarantee on the ordering of the operations. And SQL Server decides to do something different. Namely, it tries to evaluate 'apple' as a date.

The right solution for both is to cast before the compare. Because the cast may not work, you need to be careful. You should use case in SQL Server for this purpose; it is the only construct that guarantees sequential evaluation.

So, try this:

;WITH CTE AS
 (
    SELECT *
    FROM DatesToValidate
    WHERE I NOT IN (    SELECT ID
                        FROM Errors)
 )
 SELECT *
 FROM CTE
 WHERE (case when isdate(IDate) = 1 then cast(IDate as date)
        end) NOT IN (   SELECT VDate
                        FROM ValidDate)

Autres conseils

This particular part is failing cause VDate is a date type column and IDate is varchar. So you need to cast VDate like cast(VDate as varchar(30) as shown below.

See a demo fiddle here http://sqlfiddle.com/#!3/abfc0/7

WHERE IDate NOT IN (SELECT VDate FROM ValidDate)  <--- Failing

Try your sql this way

;WITH CTE AS
 (
    SELECT *
    FROM DatesToValidate
    WHERE I NOT IN (    SELECT ID
                        FROM Errors)
 )

 SELECT *
 FROM CTE
 WHERE IDate NOT IN (SELECT cast(VDate as varchar(30))  <--- Cast Here
                        FROM ValidDate)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top