什么是謂詞?
SQL 的保留字中,有很多都被歸為謂詞一類。例如,“=、<、>”等比較謂詞,以及 BETWEEN、LIKE、IN、IS NULL 等。
謂詞是一種特殊的函數,返回值是真值。前面提到的每個謂詞,返回值都是 true、false 或者 unknown(一般的謂詞邏輯里沒有unknown,但是 SQL 采用的是三值邏輯,因此具有三種真值)。
1、SQL中的bool類型的值有三種
普通編程語言里的布爾型只有 true
和 false
兩個值,這種邏輯體系被稱為二值邏輯。而 SQL 語言里,除此之外還有第三個值 unknown
,因此這種邏輯體系被稱為三值邏輯(three-valued logic)。
三個真值之間有下面這樣的優先級順序。
AND 的情況:false > unknown > true
OR 的情況:true > unknown > false
優先級高的真值會決定計算結果。例如 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
謂詞永遠不會返回 unknown
。EXISTS
只會返回 true
或者 false
。
因此就有了 IN
和 EXISTS
可以互相替換使用,而 NOT IN
和 NOT 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進價教程》
更新於:2023-4-6