2011-03-04 49 views
9

我一直在試圖理解爲什麼我用SQL查詢得到「除以零」(Msg 8134),但我必須缺少一些東西。我想想知道爲什麼下面的具體情況,我尋找NULLIFCASE WHEN...或類似的,因爲我已經知道他們(當然可以用他們在局勢下一個)。儘管沒有包含0的列,TSQL除以零

我有類似

SELECT 
    TotalSize, 
    FreeSpace, 
    (FreeSpace/TotalSize * 100) 
FROM 
    tblComputer 
...[ couple of joins ]... 
WHERE 
    SomeCondition = SomeValue 

運行與上面提到的錯誤消息,這,本身是沒有問題的這種說法的錯誤計算列的SQL語句 - 顯然TotalSize很可能是0,因此導致錯誤。

現在我不明白的是,我沒有任何行TotalSize列是0時,我評論計算列出來,我再次檢查,這是不是這種情況。

後來我想,由於某種原因列計算將在整個結果進行設定其實之前與where子句條件的過濾,但是這一個)是沒有意義恕我直言,和b)當試圖重現與測試誤差建立一切工作正常(見下文):

INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0001',1) 
INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0002',1) 
INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0003',1) 
INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0004',0) 
INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0005',1) 
INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0006',0) 
INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0007',1) 

INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (1,100,21) 
INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (2,100,10) 
INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (3,100,55) 
INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (4,0,10) 
INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (5,100,23) 
INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (6,100,18) 
INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (7,100,11) 

-- This statement does not throw an error as apparently the row for ComputerID 4 
-- is filtered out before computing the (FreeSpace/TotalSize * 100) 
SELECT 
TotalSize, 
FreeSpace, 
(FreeSpace/TotalSize * 100) 
FROM 
tblComputer 
JOIN 
tblHDD ON 
tblComputer.ID = tblHDD.ComputerID 
WHERE 
IsServer = 1 

我很爲難,想知道是什麼原因。

任何意見或指針到正確的方向是非常歡迎的,在此先感謝

更新

謝謝到目前爲止您的輸入,但不幸的是我似乎沒有越來越接近根的問題。我設法將這個語句拆分了一些,現在有了這樣的情況,如果刪除了一個JOIN,我可以無誤地執行它(我將暫時刪除輸出中的其他列)。

我不明白,爲什麼使用JOIN導致錯誤,應該不是一個標準的INNER JOIN總是要麼返回相同的行數或,但從來沒有

工作代碼

SELECT 
TotalSize, 
FreeSpace 
((FreeSpace/TotalSize) * 100) 
FROM 
MyTable1 
INNER JOIN 
MyTable2 ON 
MyTable1.ID = MyTable2.Table1ID 
WHERE 
SomeCondition 

錯誤導致代碼

SELECT 
TotalSize, 
FreeSpace 
((FreeSpace/TotalSize) * 100) 
FROM 
MyTable1 
INNER JOIN 
MyTable2 ON 
MyTable1.ID = MyTable2.Table1ID 
-- This JOIN causes "divide by zero encountered" error 
INNER JOIN 
MyTable3 ON 
MyTable2.ID = MyTable3.Table2ID 
WHERE 
SomeCondition 

我使用遊標並遍歷按行結果行也試過我的運氣,但在這種情況下,沒有錯誤發生了(不管,上面兩個陳述中的哪一個我試過)。

對不起,亂碼縮進,不知何故正確的格式似乎並沒有被應用。

G.

回答

13

SQL是聲明性語言;您編寫了一個邏輯描述所需結果的查詢,但是由優化器來制定物理計劃。這個物理計劃可能與查詢的書面形式沒有多大關係,因爲優化器不會簡單地對從查詢的文本形式派生的「步驟」進行重新排序,它可以應用300多種不同的轉換來找到有效的執行策略。

優化程序對錶達式,連接和其他邏輯查詢構造進行重新排序具有相當大的自由度。這意味着,一般來說,你不能依賴任何書面查詢表單來強制一件事先被評估。特別是,列文給出的重寫確實是而不是強制在表達式之前評估WHERE子句謂詞。根據成本估算,優化器可以決定評估表達式,看起來效率最高的表達式。這在某些情況下甚至可能意味着該表達式不止一次被評估。

最初的問題考慮了這種可能性,但拒絕它作爲「沒有多大意義」。儘管如此,這是產品的工作方式 - 如果SQL Server估計連接會減小設置的大小,足以使計算連接結果的表達式更便宜,則可以自由進行。

一般規則是永遠不要依賴特定的評估順序來避免像溢出或零除錯誤之類的事情。在這個例子中,可以使用CASE語句來檢查零除數 - 防禦性編程的一個例子。

優化程序對事物重新排序的自由是其設計的基本原則。您可以找到導致違反直覺行爲的案例,但總體而言,這些好處遠大於弊。

Paul

+1

謝謝您的解釋,它證實了我在過去幾天內開始假設的內容以及Lieven在上述評論中提出的建議。由於我只能選擇一個答案,而且他更多地參與了此案,所以我選擇了他的答案,但再次感謝您的幫助。 – Gorgsenegger 2011-03-08 08:12:22

+2

+1。 @Gorgsenegger - SQLkiwi的回答很有用,也回答了我自己試圖強制優化器首先評估where子句的錯誤。我感謝您的信任投票,但您真的應該將* this *標記爲已接受的答案。 – 2011-03-08 08:19:29

+0

@Lieven,好的,如果這對你來說沒有問題,謝謝不管怎麼說;-) – Gorgsenegger 2011-03-08 09:08:10

1

什麼你在運行時將返回行:

SELECT 
    TotalSize 
FROM 
    tblComputer 
    ...[ couple of joins ]... 
WHERE 
    SomeCondition = SomeValue 
    and ((TotalSize * 100) = 0) 

這可能給你一個線索,如何SQL即成RIS評估(總計TOTALSIZE * 100)爲零。

另一個想法是,你的where語句中有什麼可能也是問題嗎?
你假設它是TotalSize,但它可能在其他地方。

+0

感謝這個建議。它並沒有幫助我在這裏的情況,但值得記住,因爲它可能會在稍後有所幫助:-) – Gorgsenegger 2011-03-04 12:51:33

4

The basic steps that SQL Server uses處理單個SELECT語句包括以下

  1. 分析器掃描SELECT語句,並把它分成邏輯 單元,諸如關鍵字,表達式, 運算符和標識符。
  2. 建立了一個查詢樹,有時稱爲序列樹,它描述了 將源數據轉換爲結果集所需的格式 所需要的邏輯步驟 。
  3. 查詢優化器分析可以訪問源表的 的不同方式。然後它選擇 系列步驟,返回 結果最快,同時使用較少的 資源。查詢樹更新爲 以記錄此確切的一系列步驟。 查詢樹的最終優化版本稱爲執行 計劃。
  4. 關係引擎開始執行執行計劃。由於需要處理來自基表 表的數據的 步驟,因此關係 引擎請求存儲引擎將數據從關係引擎請求的行集合 中傳遞出去。
  5. 關係引擎將從存儲 引擎返回的數據處理爲爲 結果集定義的格式,並將結果集 返回給客戶端。

我對事物的解釋是,有不能保證您的where子句獲取對所有行評估計算列前評估。

您可以通過改變您的查詢來驗證該假設,如下所示,並強制在計算之前對where子句進行評估。

SELECT 
    TotalSize, 
    FreeSpace, 
    (FreeSpace/TotalSize * 100) 
FROM (
    SELECT 
     TotalSize, 
     FreeSpace, 
    FROM 
     tblComputer 
    ...[ couple of joins ]... 
    WHERE 
     SomeCondition = SomeValue 
) t 
+2

+1是的。將計算標量放在過濾器之前或之後是免費的。我記得Remus Rusanu對此有一個很好的回答,但現在找不到它。 – 2011-03-04 12:35:56

+0

感謝您的回覆,我更新了原文,以反映我的最新發現。不幸的是,我仍然沒有想出這個問題的答案/原因,雖然... – Gorgsenegger 2011-03-04 12:53:07

+0

@Gorgsenegger,答案是它幾乎完全取決於優化器如何以及何時選擇評估計算。使用你的小樣本,它選擇評估where子句後的計算*。在你的原創中,這是相反的。您是否按照我的建議嘗試更改查詢? – 2011-03-04 13:25:06

0

我遇到了同樣的問題。在我的情況下空值是可以接受的,所以我能夠解決這樣說:

Select Expression1/Expression2 -- Caused Division By 0 
Select Expression1/NULLIF(Expression2,0) -- Causes result to be NULL 

如果你需要其他的處理,你可以用整個表達式中的ISNULL函數是這樣的:

Select ISNULL(Expression1/NULLIF(Expression2,0)-5) -- Returns -5 instead of null or divide by 0 
+0

謝謝,但正如我在我的問題中所說的,我一直在尋找「爲什麼」,而不是用你的建議來解決它。 – Gorgsenegger 2012-02-05 15:25:59

+0

不確定我在第一次閱讀時如何錯過。另一個答案几乎涵蓋了它 - 它取決於運行時生成的執行計劃。我剛剛遇到了這種我曾經錯過的觀點 - 很長一段時間,但有人最終提出了一個反對它的查詢,這改變了計劃,導致了一個錯誤。 – Bytemaster 2012-03-02 00:04:13

相關問題