1. 程式人生 > 其它 >SQL總複習三:true、false、unknown 和 null

SQL總複習三:true、false、unknown 和 null

什麼是謂詞?

SQL 的保留字中,有很多都被歸為謂詞一類。例如,“=、<、>”等比較謂詞,以及 BETWEEN、LIKE、IN、IS NULL 等。

謂詞是一種特殊的函式,返回值是真值。前面提到的每個謂詞,返回值都是 truefalse 或者 unknown(一般的謂詞邏輯裡沒有unknown,但是 SQL 採用的是三值邏輯,因此具有三種真值)。

1、SQL中的bool型別的值有三種

普通程式語言裡的布林型只有 truefalse 兩個值,這種邏輯體系被稱為二值邏輯。而 SQL 語言裡,除此之外還有第三個值 unknown,因此這種邏輯體系被稱為三值邏輯(three-valued logic)。

三個真值之間有下面這樣的優先順序順序。

AND 的情況:falseunknowntrue

OR 的情況:trueunknownfalse

優先順序高的真值會決定計算結果。例如 true AND unknown,因為unknown的優先順序更高,所以結果是 unknown。而true OR unknown的話,因為 true 優先順序更高,所以結果是 true

unknown是因關係資料庫採用了 NULL 而被引入的,他不是“未知”的這個意思,而是“無意義”的這個意思。而null是指“未知”的意思。

注意:unknown不能像true或者false一樣,直接在SQL中使用,比如where Tel=unknown

2、null不是值,null與數學運算子一起使用的結果永遠是unknown

為什麼對 NULL 使用比較謂詞後得到的結果永遠不可能為真呢?這是因為,NULL 既不是值也不是變數。NULL 只是一個表示“沒有值”的標記,而比較謂詞只適用於值。因此,對並非值的 NULL 使用比較謂詞本來就是沒有意義的。

常聽到的“列的值為 NULL” 、“NULL 值”這樣的說法本身就是錯誤的。因為 NULL 不是值!(如果有人認為 NULL 是值,那麼它是什麼型別的值?關係資料庫中存在的值必然屬於某種型別,比如字元型或數值型等。所以,假如 NULL 是值,那麼它就必須屬於某種型別。)

消除 NULL 的具體方法,這裡總結如下。

(1) 首先分析能不能設定預設值。

(2) 僅在無論如何都無法設定預設值時允許使用 NULL

筆者認為,如果遵守這兩條原則,那就足以避免 NULL 帶來的各種問題,使系統開發能夠更加順利地進行。

另外,注意:要想 和 null 比較 只能用 is null 或者 is not null,這樣才會返回true或者false。另外永遠記住一點,null和<,>,=,<>這些放在一起結果永遠是unknown,比如如 2=null,結果肯定是unknown,而unknown在三值邏輯中不是true也不是false,在寫where子句的篩選條件時尤其要注意。舉例來講:

我們經常會遇到判斷篩選條件的結果(為true/false/unknown的一種,且SQL只會取返回結果是true的記錄),它們通常是and 或or連線這些單個條件的,如:where age>18 and sex=0或where age<18 or sex =unknown。

請務必牢記

and運算,只要有一邊是unknown,另一邊是false,那結果就是false,其它情況下,只要任意一邊有unknown,結果就是unknown。

or運算,只要一邊是unknown,那麼結果永遠就是unknown

not unknown 的結果是 unknown

case與null

當case使用的變數或列的值可能為null時,唯一正確的使用方式如下:

CASE 
	WHEN col_1 = 1 THEN '○'
 	WHEN col_1 IS NULL THEN '×'
END

而不是:

CASE col_1
 WHEN 1 THEN '○'
 WHEN NULL THEN '×'
END

3、NOT IN 和 NOT EXISTS 不是等價的

如果 NOT IN 子查詢中用到的表裡被選擇的列中存在 NULL,則 SQL 語句整體的查詢結果永遠是空

EXISTS 謂詞永遠不會返回 unknownEXISTS 只會返回 true 或者 false

因此就有了 INEXISTS 可以互相替換使用,而 NOT INNOT EXISTS 卻不可以互相替換的混亂現象。

4、ALL運算子與null

以下是ALL運算子語法:

scalar_expression comparison_operator ALL ( subquery )

在上面語法中,

  • scalar_expression是任何有效的表示式。
  • comparison_operator是任何有效的比較運算子,包括等於(=),不等於(<>),大於(>),大於或等於(>=),小於(<),小於或等於(<=)。
  • 括號內的子查詢(subquery)是一個SELECT語句,它返回單個列的結果。 此外,返回列的資料型別必須與標量表達式的資料型別相同。

如果all裡面的子查詢返回的單列中有null的存在,那麼這個all表示式就永遠不會篩選出任何資料,結果肯定為空。

因為ALL 謂詞其實是多個以 AND 連線的邏輯表示式的省略寫法

如果all裡面的子查詢返回的單列中有null的存在,比如子查詢結果如下面這個情況,那麼具體的分析步驟如下所示。

--1. 執行子查詢獲取年齡列表
SELECT *
  FROM Class_A
 WHERE age < ALL ( 22, 23, NULL );
--2. 將ALL 謂詞等價改寫為AND
SELECT *
  FROM Class_A
 WHERE (age < 22) AND (age < 23) AND (age < NULL);
--3. 對NULL 使用“<”後,結果變為 unknown
SELECT *
  FROM Class_A
 WHERE (age < 22) AND (age < 23) AND unknown;
--4. 如果AND 運算裡包含unknown,則結果不為true
SELECT *
  FROM Class_A
 WHERE false 或 unknown;
--5.查詢結果為空

5、極值函式(max, min)、count以外的聚合函式(sum,average)與null

極值函式在輸入為空表(空集)時會返回 NULL。即從一個為空的集合中,選取最大或最小值,會得到null。

因此,建議在使用這些可能返回null的函式的地方,外面套一層isnull函式(SqlServer)來處理

count與null

COUNT 函式的使用方法有 COUNT(*) 和 COUNT( 列名 ) 兩種,它們的區別有兩個:第一個是效能上的區別;第二個是 COUNT(*) 可以用於 NULL, 而 COUNT( 列名 )與其他聚合函式一樣,要先排除掉 NULL 的行再進行統計。

第二個區別也可以這麼理解:COUNT(*) 查詢的是所有行的數目,而COUNT( 列名 ) 查詢的則不一定是。


參考書籍:圖靈社群的《SQL進價教程》