2013-12-18 59 views
-1

在嘗試回答Stack Overflow上的另一個SQL Server問題時,遇到了一些無法正常工作的情況。我正在使用SQL Server 2008R2,但這可能並不重要。在查詢派生表時在SQL Server中拋出異常

我有一個簡單的假設表,有一些日期存儲爲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'); 

我想選擇假期的7月1日之前的數量,2013年我懷疑日期可能無效,所以我必須爲此規劃以避免例外。之前,我甚至寫這些下面的查詢,我知道他們會失敗:

-- 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'; 
+1

這可能是MS SQL Server查詢優化器中的錯誤。如果您查看執行計劃,您會看到首先轉換所有字符串:CONVERT(date,[tempdb]。[dbo]。[#temp]。[DateString],0)<'2013-07-01' – alex

+1

請參閱http://connect.microsoft.com/SQLServer/feedback/details/537419/sql-server-should-not-raise-illogical-errors –

回答

2

SQL Server足夠聰明,可以查看您正在做什麼,並結合內部和外部查詢以使其更有效。你將不得不迫使它做兩次傳球。

使用內部查詢來選擇一個臨時表,然後在外部查詢中使用它。

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