While trying to answer another SQL Server question on Stack Overflow I encountered something that does not work as I expected. I am using SQL Server 2008R2, but this is probably not important.
I have a simple hypothetical table that has some dates stored as VARCHAR datatype. I know it is obvious and practical to store date information using the DATE datatype, but this example uses a VARCHAR intentionally to demonstrate a problem I encountered. The reason the date can be invalid is not important - it could have been bad sanitization, malformed update query, use your imagination, etc.
-- 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');
I want to select the number of holidays before July 1st, 2013. I suspect the dates may be invalid so I must plan for that to avoid exceptions. Before I even write these following queries, I know they will fail:
-- 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';
This query works as expected and would be my final choice:
-- works
SELECT COUNT(*) AS [CountHolidays]
FROM #temp
WHERE CONVERT(DATE,CASE WHEN ISDATE(DateString)=1 THEN datestring ELSE NULL END) < '20130701';
Before I wrote my final query though, I tried this first and I expected it to work, but it also raises a cast exception. Why does this query fail specifically?
-- 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';