查询派生表时在SQL Server中强制转换异常

时间:2013-12-18 16:38:14

标签: sql sql-server sql-server-2008 tsql derived-table

在尝试回答Stack Overflow上的另一个SQL Server问题时,我遇到了一些无法正常工作的问题。我使用的是SQL Server 2008R2,但这可能并不重要。

我有一个简单的假设表,其中一些日期存储为VARCHAR数据类型。我知道使用DATE数据类型存储日期信息是显而易见和实用的,但是此示例有意使用VARCHAR来演示我遇到的问题。日期可能无效的原因并不重要 - 可能是糟糕的清理,更新的查询格式错误,使用您的想象力等等。

-- setup
CREATE TABLE #temp (DateString VARCHAR(10)); 
INSERT #temp (DateString) VALUES ('01/01/2013');
INSERT #temp (DateString) VALUES ('02/14/2013');
INSERT #temp (DateString) VALUES ('03/31/2013');
INSERT #temp (DateString) VALUES ('05/27/2013');
INSERT #temp (DateString) VALUES ('06/31/201'); -- known invalid date, maybe the data wasn't sanitized, etc.
INSERT #temp (DateString) VALUES ('07/04/2013');

我想在2013年7月1日之前选择假期数量。我怀疑日期可能无效,所以我必须为此计划以避免例外情况。在我编写以下查询之前,我知道它们会失败:

-- fails: cast exception, obviously
SELECT COUNT(*) AS [CountHolidays]
FROM #temp
WHERE CAST(DateString AS DATE)<'20130701';

-- fails: ISDATE() is not gauranteed to be evaluated first, less obvious, pointed out by another user.
SELECT COUNT(*) AS [CountHolidays]
FROM #temp
WHERE ISDATE(DateString) = 1 AND CAST(DateString AS DATE)<'20130701';

此查询按预期工作,将是我的最终选择:

-- works
SELECT COUNT(*) AS [CountHolidays]
FROM #temp
WHERE CONVERT(DATE,CASE WHEN ISDATE(DateString)=1 THEN datestring ELSE NULL END) < '20130701';

在我写完最后一个查询之前,我先尝试过这个,我希望它可以工作,但它也会引发一个强制转换异常。为什么此查询失败?

-- Why does this query fail specifically?
-- I expected my derived inner query to filter invalid dates out first, but it does not. I get the same cast exception.
SELECT COUNT(*) AS [CountHolidays]
FROM (
    -- the derived table returns expected data when executed independently.
    SELECT DateString
    FROM #temp
    WHERE ISDATE(DateString) = 1
) AS T
WHERE CAST(DateString AS DATE)<'20130701';

2 个答案:

答案 0 :(得分:2)

SQL Server非常聪明,可以看到您正在做什么,并结合内部和外部查询,以提高效率。你将不得不强迫它做两次通过。

使用内部查询选择临时表,然后在外部查询中使用它。

答案 1 :(得分:1)

通过明确的日期转换,您将获得预期的结果。

SELECT COUNT(*) AS [CountHolidays] 
FROM (SELECT DateString FROM #temp WHERE ISDATE(DateString) = 1) AS T
WHERE CAST(DateString AS DATE)<(select CAST('20130701' AS DATE));

以下查询本身返回结果(空)但不提供计数。因为在此之前,日期转换失败,并将错误消息显示为“从字符串转换日期和/或时间时转换失败”。 如果您尝试使用'select *',您将获得结果,然后会显示错误消息。

SELECT COUNT(*) AS [CountHolidays] 
FROM (SELECT DateString FROM #temp WHERE ISDATE(DateString) = 1) AS T
WHERE CAST(DateString AS DATE)<'20130701';

根据连接语言设置,可以不同方式解释日期字段。它依赖于SQL Server语言。每种语言的日期格式都有不同的解释。因此明确转换日期格式会更好。要检查语言设置和其他选项,请使用以下查询。

DBCC USEROPTIONS