CTE导致转换从字符串错误转换日期和/或时间时失败

时间:2014-02-28 17:04:21

标签: sql sql-server-2008 tsql common-table-expression

我创建了一个非常简单的示例,显示了在使用动态SQL的更复杂的查询中遇到的问题,我无法弄清楚为什么CTE在标准版本没有时会导致错误。

使用CTE时出错:

  

从字符串转换日期和/或时间时转换失败。

我正在尝试使用动态查询来实现以下功能: 标记与指定数据类型不匹配的记录(在本例中为日期)。如果他们通过该步骤,则稍后会根据查找表对其进行检查,以验证它们是我期望的值(验证指定范围内的日期)。

 -- 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

以下是标准查询(Works):

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

以下是CTE(不起作用):

 ;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)

2 个答案:

答案 0 :(得分:3)

This SQL Fiddle显示了问题。这两个版本都不能保证有效。如果您在and中交换条件的顺序,那么您将获得转换失败。

在这种情况下and版本发生的原因是因为短路。当第一个条件失败时,第二个条件不会运行。这是SQL Server 可以利用的优化。在这种情况下,你认为它是否有效。

CTE版本不起作用,因为无法保证操作的顺序。 SQL Server决定做一些不同的事情。也就是说,它会尝试将'apple'评估为日期。

两者的正确解决方案是在比较之前进行投射。因为cast可能不起作用,所以您需要小心。为此,您应该在SQL Server中使用case;它是唯一保证顺序评估的结构。

所以,试试这个:

;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)

答案 1 :(得分:1)

此特定部分失败,因为VDatedate类型列,IDatevarchar。因此,您需要像VDate一样投射cast(VDate as varchar(30),如下所示。

在此处查看演示小提琴http://sqlfiddle.com/#!3/abfc0/7

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

以这种方式尝试你的SQL

;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)