我创建了一个非常简单的示例,显示了在使用动态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)
答案 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)
此特定部分失败,因为VDate
是date
类型列,IDate
是varchar
。因此,您需要像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)