2011-12-21 30 views
9

我從SQL Server的多年工作中得到了一個模糊的,可能是貨運信息的內存,當你有一個可能爲null的列時,編寫「WHERE」子句是不安全的謂詞,如:SQL和邏輯運算符以及空值檢查

... WHERE the_column IS NULL OR the_column < 10 ... 

它有事可做的事實,SQL規則沒有規定短路(事實上這是一個壞主意種,一種可能的查詢優化的原因),因此「 <「比較(或其他)即使列值爲空也可以被評估。現在,正是爲什麼這會是一件可怕的事情,我不知道,但我記得一些文件被正告總是代碼爲「CASE」條款:

... WHERE 1 = CASE WHEN the_column IS NULL THEN 1 WHEN the_column < 10 THEN 1 ELSE 0 END ... 

(愚蠢的「1 =」部分是因爲SQL Server不/沒有一流的布爾值,或者至少我認爲它沒有)

所以在這裏我的問題是:

  1. 是這對於SQL Server(或者可能是SQL Server 2000或2005的後端版本)來說確實如此,或者我只是瘋了嗎?
  2. 如果是這樣,對PostgreSQL的適用警告是否適用? (8.4如果重要)
  3. 究竟是什麼問題?它是否與索引如何工作有關?

我在SQL中的基礎很弱。

+1

也許他們都在談論和?由於null和任何內容都爲空,因此在表達式可能包含空項的情況下,經常需要合併或案例。 – 2011-12-21 08:19:18

回答

10

我不知道SQL服務器,所以我不能到說話。

鑑於一些邏輯運算符L表達a L b,也不能保證a會前或b後,甚至雙方ab將被評估進行評估:

Expression Evaluation Rules

的沒有定義子表達式的評估順序。特別是,操作員或功能的輸入不一定是從左到右或以任何其他固定順序進行評估。此外,如果一個表達式的結果只能通過評估它的某些部分來確定,那麼其他的子表達式可能根本就不會被評估。

請注意,這與在某些編程語言中發現的布爾運算符從左到右的「短路」不同。

因此,使用具有副作用的函數作爲複雜表達式的一部分是不明智的。依靠WHEREHAVING條款中的副作用或評估順序是特別危險的,因爲這些條款作爲制定執行計劃的一部分而被廣泛地重新處理。

至於形式的表達式:

the_column IS NULL OR the_column < 10 

而言,沒有什麼可擔心的,因爲NULL < nNULL所有n,甚至NULL < NULL計算結果爲NULL;此外,NULL是不是真的那麼

null is null or null < 10 

是說true or null的只是一種複雜的方式,這就是true無論哪個子表達式先求的。

整個「使用CASE」的聲音聽起來像貨物崇拜SQL對我來說。然而,像大多數貨物邪教一樣,貨物下面埋藏着一個真相,略低於我在PostgreSQL手冊第一摘錄,你會發現這一點:

當它是必要的強制評估順序,一個CASE結構(見9.16)都可以使用。例如,這是試圖避免被零除在WHERE第一個不可信賴的方式:

SELECT ... WHERE x > 0 AND y/x > 1.5; 

但是,這是安全的:

SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END; 

所以,如果你需要警惕條件會引發異常或產生其他副作用,那麼您應該使用CASE來控制評估順序爲CASEevaluated in order

每個條件是一個返回boolean結果的表達式。如果條件結果爲真,CASE表達式的值是該條件之後的結果,並且不處理CASE表達式的其餘部分。如果條件的結果不成立,則以相同的方式檢查後續的WHEN子句。

所以給出這樣的:

case when A then Ra 
    when B then Rb 
    when C then Rc 
    ... 

A是保證評估B之前,BC等,並評估之前儘快的條件之一計算爲真值停止。

總之,CASE短路的擊打既不AND也不OR短路,所以你只需要使用一個CASE當你需要防止副作用。

+1

是的,謝謝;我瞭解SQL不強制實施短路(或者,「非短路」)規則。問題的關鍵在於,如果普通的關係比較是針對可能爲null的列進行評估,那麼是否會發生可怕的事情。感謝您提供非常詳細的答案。 – Pointy 2011-12-21 04:47:12

1

我從來沒有聽說過這樣的問題,this bit of SQL Server 2000 documentation在一個例子中使用WHERE advance < $5000 OR advance IS NULL,所以它一定不是一個非常嚴厲的規則。我唯一關心的是OR,它的優先級低於AND,所以如果不是你的意思,你可能會意外地寫出類似WHERE the_column IS NULL OR the_column < 10 AND the_other_column > 20的東西;但通常的解決方案是括號而不是大的CASE表達式。

我認爲在大多數RDBMS中,索引不包含空值,因此the_column上的索引對於此查詢不會非常有用;但即使不是這樣,我也不明白爲什麼一個大的CASE表達式對索引更友好。

(當然,這是很難證明負面的,也許別人會知道你指的是什麼?)

1

嗯,我已經多次因爲是永遠的寫的第一個例子查詢(哎呀,我已經寫了產生這樣的查詢的查詢生成器),我從來沒有遇到過的問題。

我想你可能記得有人給你一些警告,反對寫作時髦的加入條件使用OR。在你的第一個例子中,由OR加入的條件限制了同一個表的同一列,這是可以的。如果你的第二個條件是連接條件(即,它限制列來自兩個不同的表),那麼你可以進入壞的情況下查詢規劃只是沒有選擇,只能使用一個笛卡兒連接(壞,壞,壞的! )。

我不認爲你的情況的功能實在是做任何事情在那裏,除了在尋找的查詢良好的執行計劃可能妨礙你的查詢規劃的企圖。

但更普遍,只是先寫簡單的查詢,看看它是如何執行的真實數據。無需擔心可能不存在的問題!

0

空值可能會造成混淆。如果您試圖傳遞Null或Value作爲參數ex,則「... WHERE 1 = CASE ...」非常有用。 「WHERE the_column = @parameter。這篇文章可能會有所幫助Passing Null using OLEDB

1

而不是

the_column IS NULL OR the_column < 10 

我做

isnull(the_column,0) < 10 

或第一個例子

WHERE 1 = CASE WHEN isnull(the_column,0) < 10 THEN 1 ELSE 0 END ... 
0

CASE有用的另一個例子是在varchar列上使用日期函數時,在u之前添加ISDATE唱歌說轉換(colA,datetime)可能不起作用,並且當colA有非日期數據時,查詢可能會出錯。