一文詳解SQL?中的三值邏輯
1. 前言
大多數(shù)編程語言都是基于二值邏輯的,即邏輯真值只有真和假兩個。而 SQL 語言則采用一種特別的邏輯體系——三值邏輯,即邏輯真值除了真和假,而 SQL 語言里,除此之外還有第三個值 unknown
,因此這種邏輯體系被稱為三值邏輯(three-valued-logic)。
2. 兩種 Null
- 表示未知 (unknown):“不知道戴墨鏡的人眼睛是什么顏色”這種情況為例,這個人的眼睛肯定是有顏色的,但是如果他不摘掉眼鏡,別人就不知道他的眼睛是什么顏色。這就叫作未知。
- 表示不適用 (not applicable 或 inapplicable):“不知道冰箱的眼睛是什么顏色”則屬于“不適用”。因為冰箱根本就沒有眼睛,所以“眼睛的顏色”這一屬性并不適用于冰箱。
“冰箱的眼睛的顏色”這種說法和“圓的體積”“男性的分娩次數(shù)”一樣,都是沒有意義的。平時,我們習(xí)慣了說“不知道”,但是“不知道”也分很多種。“不適用”這種情況下的 NULL ,在語義上更接近于“無意義”,而不是“不確定”。
總結(jié):“未知”指的是“雖然現(xiàn)在不知道,但加上某些條件后就可以知道”;而“不適用”指的是“沒有辦法知道”。
3. 為什么是 is Null 而不是 = Null ?
“我們先從定義一個表示‘雖然丟失了,但卻適用的值’的標(biāo)記開始。我們把它叫作 A-Mark。這個標(biāo)記在關(guān)系數(shù)據(jù)庫里既不被當(dāng)作值(value),也不被當(dāng)作變量 (variable)。”(E.F. Codd,The Relational Model for Database Management :Version 2 , P.173) “關(guān)于 NULL 的很重要的一件事情是,NULL 并不是值。”(C.J. Date, An Intruction To Database System (6th edition ), P.619)
對 NULL 使用比較謂詞后得到的結(jié)果總是 unknown 。而查詢結(jié)果只會包含 WHERE 子句里的判斷結(jié)果為 true 的行,不會包含判斷結(jié)果為 false 和 unknown 的行。不只是等號,對 NULL 使用其他比較謂詞,結(jié)果也都是一樣的。
-- 以下的式子都會被判為 unknown 1 = NULL 2 > NULL 3 < NULL 4 <> NULL NULL = NULL
那么,為什么對 NULL 使用比較謂詞后得到的結(jié)果永遠(yuǎn)不可能為真呢?這是因為,NULL 既不是值也不是變量。NULL 只是一個表示“沒有值”的標(biāo)記,而比較謂詞只適用于值。因此,對并非值的 NULL 使用比較謂詞本來就是沒有意義的。(Null只是一個作為區(qū)分的標(biāo)記,并不是一個值)
“列的值為 NULL ”“NULL 值”這樣的說法本身就是錯誤的。因為 NULL 不是值,所以不在定義域(domain)中。相反,如果有人認(rèn)為 NULL 是值,那么請區(qū)分一下:它是什么類型的值?關(guān)系數(shù)據(jù)庫中存在的值必然屬于某種類型,比如字符型或數(shù)值型等。所以,假如 NULL 是值,那么它就必須屬于某種類型。( SQL 里的 NULL 和其他編程語言里的 NULL 是完全不同的東西)
4. 第三個真值 “unknown”
因關(guān)系數(shù)據(jù)庫采用了 NULL 而被引入了 “第三個真值”。這里有一點需要注意:真值 unknown 和作為 NULL 的一種的 UNKNOWN (未知)是不同的東西。前者是明確的布爾型的真值,后者既不是值也不是變量。(下文使用 unknown 表示 真值,UNKNOWN 表示 代表Null的一個 標(biāo)記)
舉個栗子: unknown = unknown
判定為 true
而 UNKNOWN = UNKNOWN
( 也就是 Null = Null
) 判定為 unknown
5. 包含三值邏輯的真值表
當(dāng)兩個值進行邏輯判斷的時候的優(yōu)先級,優(yōu)先級高的真值會決定計算結(jié)果:
- AND 的情況: false > unknown > true
- OR 的情況: true > unknown > false
舉個栗子: true AND unknown ,因為 unknown 的優(yōu)先級更高,所以結(jié)果是 unknown 。而 true OR unknown 的話,因為 true 優(yōu)先級更高,所以結(jié)果是 true 。
6. “排中律” 不再成立
“把命題和它的否命題通過‘或者’連接而成的命題全都是真命題” 這個命題在二值邏輯中被稱為排中律(Law of Excluded Middle)。顧名思義,排中律就是指不認(rèn)可中間狀態(tài),對命題真?zhèn)蔚呐卸ê诎追置鳎枪诺溥壿媽W(xué)的重要原理。
舉個栗子:現(xiàn)實生活中 一個學(xué)生 是20歲 或者 不是20歲,不會有第三種情況。
但是在SQL中并不是這個樣:
-- 查詢年齡是20 歲或者不是20 歲的學(xué)生 SELECT * FROM Students WHERE age = 20 OR age <> 20;
在現(xiàn)實生活中,上面的查詢條件應(yīng)該包含所有的學(xué)生,但是這里的執(zhí)行結(jié)果并不會查詢到約翰。
--- 當(dāng)查詢到約翰哪一行時的判定 WHERE Null = 20 OR Null <> 20 --- 根據(jù)上文的描述,該條件會轉(zhuǎn)換為 WHERE unknown OR unknown 等同于 WHERE unknown
若要查到所有學(xué)生需要再加上一個條件:OR age IS NULL
7. CASE 表達式和 NULL
CASE col_1 WHEN 1 THEN '○' WHEN NULL THEN '×' END
上面的這個CASE表達式會在 col_1 為 1 時返回 ○
、為 NULL 時返回 ×
嗎?顯然始終不會返回 x
因為第二個 WHEN 子句是 col_1 = NULL 的縮寫形式,根據(jù)上文的描述 col_1 = NULL 始終會返回 unknown 而 CASE 表達式的判斷方法與 WHERE 子句一樣,只認(rèn)可真值為 true 的條件,所以 x
并不會出現(xiàn)。下面才是正確的寫法:
CASE WHEN col_1 = 1 THEN '○' WHEN col_1 IS NULL THEN '×' END
8. NOT IN 和 NOT EXISTS 不是等價的
在對 SQL 語句進行性能優(yōu)化時,經(jīng)常用到的一個技巧是將 IN 改寫成EXISTS 。這是等價改寫,并沒有什么問題。問題在于,將 NOT IN 改寫成 NOT EXISTS 時,結(jié)果未必一樣。
查詢 “與 B 班住在東京的學(xué)生年齡不同的 A 班學(xué)生” 。也就是說,希望查詢到的是拉里和伯杰。
-- 查詢與 B 班住在東京的學(xué)生年齡不同的 A 班學(xué)生的 SQL 語句 SELECT * FROM Class_A WHERE age NOT IN ( SELECT age FROM Class_B WHERE city = '東京' );
這條 SQL 語句真的能正確地查詢到這兩名學(xué)生嗎?遺憾的是不能。結(jié)果是空,查詢不到任何數(shù)據(jù)。根據(jù)前文所說的規(guī)則推導(dǎo)一下吧:
--1. 執(zhí)行子查詢,獲取年齡列表 SELECT * FROM Class_A WHERE age NOT IN (22, 23, NULL); --2. 用 NOT 和 IN 等價改寫 NOT IN SELECT * FROM Class_A WHERE NOT age IN (22, 23, NULL); --3. 用 OR 等價改寫謂詞 IN SELECT * FROM Class_A WHERE NOT ( (age = 22) OR (age = 23) OR (age = NULL) ); --4. 使用德 · 摩根定律等價改寫 SELECT * FROM Class_A WHERE NOT (age = 22) AND NOT(age = 23) AND NOT (age = NULL); --5. 用<> 等價改寫 NOT 和 = SELECT * FROM Class_A WHERE (age <> 22) AND (age <> 23) AND (age <> NULL); --6. 對NULL 使用<> 后,結(jié)果為unknown SELECT * FROM Class_A WHERE (age <> 22) AND (age <> 23) AND unknown; --7.如果AND 運算里包含unknown,則結(jié)果不為true SELECT * FROM Class_A WHERE false 或 unknown;
所以 上述查詢語句 查詢不到任何數(shù)據(jù)。為了得到正確的結(jié)果,需要使用 EXISTS 謂詞。
-- 正確的SQL 語句:拉里和伯杰將被查詢到 SELECT * FROM Class_A A WHERE NOT EXISTS ( SELECT * FROM Class_B B WHERE A.age = B.age AND B.city = '東京' ); --1. 在子查詢里和NULL 進行比較運算 SELECT * FROM Class_A A WHERE NOT EXISTS ( SELECT * FROM Class_B B WHERE A.age = NULL AND B.city = '東京' ); --2. 對NULL 使用“=”后,結(jié)果為 unknown SELECT * FROM Class_A A WHERE NOT EXISTS ( SELECT * FROM Class_B B WHERE unknown AND B.city = '東京' ); --3. 如果AND 運算里包含unknown,結(jié)果不會是true SELECT * FROM Class_A A WHERE NOT EXISTS ( SELECT * FROM Class_B B WHERE false 或 unknown); --4. 子查詢沒有返回結(jié)果,因此相反地,NOT EXISTS 為true SELECT * FROM Class_A A WHERE true;
9. 限定謂詞和 NULL
-- 查詢比 B 班住在東京的所有學(xué)生年齡都小的A 班學(xué)生 這里會正常返回 拉里 SELECT * FROM Class_A WHERE age < ALL ( SELECT age FROM Class_B WHERE city = '東京' );
如果山田年齡不詳,就會有問題了。
--1. 執(zhí)行子查詢獲取年齡列表 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 使用“<”后,結(jié)果變?yōu)?unknown SELECT * FROM Class_A WHERE (age < 22) AND (age < 23) AND unknown; --4. 如果AND 運算里包含unknown,則結(jié)果不為true SELECT * FROM Class_A WHERE false 或 unknown;
10. 限定謂詞和極值函數(shù)不是等價的
將 9 中的表 Class_B 中 山田的年齡改為Null,執(zhí)行下面的查詢
-- 查詢比B 班住在東京的年齡最小的學(xué)生還要小的A 班學(xué)生 SELECT * FROM Class_A WHERE age < ( SELECT MIN(age) FROM Class_B WHERE city = '東京' );
這里仍能正確查詢出拉里和伯杰,這是因為,極值函數(shù)在統(tǒng)計時會把為 NULL 的數(shù)據(jù)排除掉。使用極值函數(shù)能使 Class_B 這張表里看起來就像不存在 NULL 一樣。
區(qū)分含義:
- ALL 謂詞:他的年齡比在東京住的所有學(xué)生都小 Q1
- 極值函數(shù):他的年齡比在東京住的年齡最小的學(xué)生還要小 Q2
Q1 和 Q2 不等價的情況:
- 表里存在 NULL 時它們是不等價的
- 謂詞(或者函數(shù))的輸入為空集的情況
這里說明一下情況2:B 班里沒有學(xué)生住在東京。這時,使用 ALL 謂詞的SQL 語句會查詢到 A 班的所有學(xué)生。然而,用極值函數(shù)查詢時一行數(shù)據(jù)都查詢不到。這是因為,極值函數(shù)在輸入為空表(空集)時會返回 NULL 。
--1. 極值函數(shù)返回NULL SELECT * FROM Class_A WHERE age < NULL; --2. 對NULL 使用“<”后結(jié)果為 unknown SELECT * FROM Class_A WHERE unknown;
11. 聚合函數(shù)和 Null
實際上,當(dāng)輸入為空表時返回 NULL 的不只是極值函數(shù),COUNT 以外的聚合函數(shù)也是如此。
-- 查詢比住在東京的學(xué)生的平均年齡還要小的A 班學(xué)生的SQL 語句? SELECT * FROM Class_A WHERE age < ( SELECT AVG(age) FROM Class_B WHERE city = '東京' );
沒有住在東京的學(xué)生時,AVG 函數(shù)返回 NULL 。因此,外側(cè)的 WHERE 子句永遠(yuǎn)是 unknown ,也就查詢不到行。
到此這篇關(guān)于一文詳解SQL 中的三值邏輯的文章就介紹到這了,更多相關(guān)SQL三值邏輯內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL中replace into與replace區(qū)別詳解
本文主要介紹了MySQL中replace into與replace區(qū)別詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08mysql數(shù)據(jù)庫備份命令分享(mysql壓縮數(shù)據(jù)庫備份)
這篇文章主要介紹了mysql數(shù)據(jù)庫備份常用語句,包括數(shù)據(jù)庫壓縮備份、備份多個MySQL數(shù)據(jù)庫、備份多個MySQL數(shù)據(jù)庫、將數(shù)據(jù)庫轉(zhuǎn)移到新服務(wù)器等語句2014-01-01win10下安裝兩個MySQL5.6.35數(shù)據(jù)庫
這篇文章主要為大家詳細(xì)介紹了win10下兩個MySQL5.6.35數(shù)據(jù)庫安裝教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05MySql 錯誤Incorrect string value for column
能使用中文進行搜索,但是insert into 中文是可以的。我的數(shù)據(jù)庫和數(shù)據(jù)表中所有的charset都是設(shè)置的utf8。2010-12-12在MySQL中創(chuàng)建帶有IN和OUT參數(shù)的存儲過程的方法
這篇文章主要介紹了在MySQL中創(chuàng)建帶有IN和OUT參數(shù)的存儲過程的方法,在一定程度上簡化了操作,需要的朋友可以參考下2015-06-06