開心一刻 git
NULL 用於表示缺失的值或遺漏的未知數據,不是某種具體類型的值。數據表中的 NULL 值表示該值所處的字段爲空,值爲 NULL 的字段沒有值,尤爲要明白的是:NULL 值與 0 或者空字符串是不一樣的。數據庫
這種說法你們可能會以爲很奇怪,由於 SQL 裏只存在一種 NULL 。然而在討論 NULL 時,咱們通常都會將它分紅兩種類型來思考:「未知」(unknown)和「不適用」(not applicable,inapplicable)。編程
以「不知道戴墨鏡的人眼睛是什麼顏色」這種狀況爲例,這我的的眼睛確定是有顏色的,可是若是他不摘掉眼鏡,別人就不知道他的眼睛是什麼顏色。這就叫做未知。而「不知道冰箱的眼睛是什麼顏色」則屬於「不適用」。由於冰箱根本就沒有眼睛,因此「眼睛的顏色」這一屬性並不適用於冰箱。「冰箱的眼睛的顏色」這種說法和「圓的體積」「男性的分娩次數」同樣,都是沒有意義的。平時,咱們習慣了說「不知道」,可是「不知道」也分不少種。「不適用」這種狀況下的 NULL ,在語義上更接近於「無心義」,而不是「不肯定」。這裏總結一下:「未知」指的是「雖然如今不知道,但加上某些條件後就能夠知道」;而「不適用」指的是「不管怎麼努力都沒法知道」。性能優化
關係模型的發明者 E.F. Codd 最早給出了這種分類。下圖是他對「丟失的信息」的分類app
我相信很多人有這樣的困惑吧,尤爲是相信剛學 SQL 的小夥伴。咱們來看個具體的案例,假設咱們有以下表以及數據dom
DROP TABLE IF EXISTS t_sample_null; CREATE TABLE t_sample_null ( id INT(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', name VARCHAR(50) NOT NULL COMMENT '名稱', remark VARCHAR(500) COMMENT '備註', primary key(id) ) COMMENT 'NULL樣例'; INSERT INTO t_sample_null(name, remark) VALUES('zhangsan', '張三'),('李四', NULL);
咱們要查詢備註爲 NULL 的記錄(爲 NULL 這種叫法自己是不對的,只是咱們平常中已經叫習慣了,具體往下看),怎麼查,不少新手會寫出這樣的 SQL編程語言
-- SQL 不報錯,但查不出結果 SELECT * FROM t_sample_null WHERE remark = NULL;
執行時不報錯,可是查不出咱們想要的結果, 這是爲何了 ? 這個問題咱們先放着,咱們往下看ide
這個三值邏輯不是三目運算,指的是三個邏輯值,有人可能有疑問了,邏輯值不是隻有真(true)和假(false)嗎,哪來的第三個? 說這話時咱們須要注意所處的環境,在主流的編程語言中(C、JAVA、Python、JS等)中,邏輯值確實只有 2 個,但在 SQL 中卻存在第三個邏輯值:unknown。這有點相似於咱們平時所說的:對、錯、不知道。函數
邏輯值 unknown 和做爲 NULL 的一種的 UNKNOWN (未知)是不一樣的東西。前者是明確的布爾型的邏輯值,後者既不是值也不是變量。爲了便於區分,前者採用小寫字母 unknown ,後者用大寫字母 UNKNOWN 來表示。爲了讓你們理解二者的不一樣,咱們來看一個 x=x 這樣的簡單等式。x 是邏輯值 unknown 時,x=x 被判斷爲 true ,而 x 是 UNKNOWN 時被判斷爲 unknown 性能
-- 這個是明確的邏輯值的比較 unknown = unknown → true -- 這個至關於NULL = NULL UNKNOWN = UNKNOWN → unknown
NOT
AND
OR
圖中藍色部分是三值邏輯中獨有的運算,這在二值邏輯中是沒有的。其他的 SQL 謂詞所有都能由這三個邏輯運算組合而來。從這個意義上講,這個幾個邏輯表能夠說是 SQL 的母體(matrix)。
NOT 的話,由於邏輯值表比較簡單,因此很好記;可是對於 AND 和 OR,由於組合出來的邏輯值較多,因此所有記住很是困難。爲了便於記憶,請注意這三個邏輯值之間有下面這樣的優先級順序。
AND 的狀況: false > unknown > true
OR 的狀況: true > unknown > false
優先級高的邏輯值會決定計算結果。例如 true AND unknown ,由於 unknown 的優先級更高,因此結果是 unknown 。而 true OR unknown 的話,由於 true 優先級更高,因此結果是 true 。記住這個順序後就能更方便地進行三值邏輯運算了。特別須要記住的是,當 AND 運算中包含 unknown 時,結果確定不會是 true (反之,若是AND 運算結果爲 true ,則參與運算的雙方必須都爲 true )。
-- 假設 a = 2, b = 5, c = NULL,下列表達式的邏輯值以下 a < b AND b > c → unknown a > b OR b < c → unknown a < b OR b < c → true NOT (b <> c) → unknown
咱們再回到問題:爲何必須寫成「IS NULL」,而不是「= NULL」
對 NULL 使用比較謂詞後獲得的結果老是 unknown 。而查詢結果只會包含 WHERE 子句裏的判斷結果爲 true 的行,不會包含判斷結果爲 false 和 unknown 的行。不僅是等號,對 NULL 使用其餘比較謂詞,結果也都是同樣的。因此不管 remark 是否是 NULL ,比較結果都是 unknown ,那麼永遠沒有結果返回。如下的式子都會被判爲 unknown
-- 如下的式子都會被判爲 unknown = NULL > NULL < NULL <> NULL NULL = NULL
那麼,爲何對 NULL 使用比較謂詞後獲得的結果永遠不可能爲真呢?這是由於,NULL 既不是值也不是變量。NULL 只是一個表示「沒有值」的標記,而比較謂詞只適用於值。所以,對並不是值的 NULL 使用比較謂詞原本就是沒有意義的。「列的值爲 NULL 」、「NULL 值」 這樣的說法自己就是錯誤的。由於 NULL不是值,因此不在定義域(domain)中。相反,若是有人認爲 NULL 是值,那麼咱們能夠倒過來想一下:它是什麼類型的值?關係數據庫中存在的值必然屬於某種類型,好比字符型或數值型等。因此,假如 NULL 是值,那麼它就必須屬於某種類型。
NULL 容易被認爲是值的緣由有兩個。第一個是高級編程語言裏面,NULL 被定義爲了一個常量(不少語言將其定義爲了整數0),這致使了咱們的混淆。可是,SQL 裏的 NULL 和其餘編程語言裏的 NULL 是徹底不一樣的東西。第二個緣由是,IS NULL 這樣的謂詞是由兩個單詞構成的,因此咱們容易把 IS 看成謂詞,而把 NULL 看成值。特別是 SQL 裏還有 IS TRUE 、IS FALSE 這樣的謂詞,咱們由此類推,從而這樣認爲也不是沒有道理。可是正如講解標準 SQL 的書裏提醒人們注意的那樣,咱們應該把 IS NULL 看做是一個謂詞。所以,寫成 IS_NULL 這樣也許更合適。
排中律不成立
排中律指同一個思惟過程當中,兩個相互矛盾的思想不能同假,必有一真,即「要麼A要麼非A」
假設咱們有學生表:t_student
DROP TABLE IF EXISTS t_student; CREATE TABLE t_student ( id INT(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', name VARCHAR(50) NOT NULL COMMENT '名稱', age INT(3) COMMENT '年齡', remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '備註', primary key(id) ) COMMENT '學生信息'; INSERT INTO t_student(name, age) VALUE('zhangsan', 25),('wangwu', 60),('bruce', 32),('yzb', NULL),('boss', 18); SELECT * FROM t_student;
表中數據 yzb 的 age 是 NULL,也就是說 yzb 的年齡未知。在現實世界裏,yzb 是 20 歲,或者不是 20 歲,兩者必居其一,這毫無疑問是一個真命題。那麼在 SQL 的世界裏了,排中律還適用嗎? 咱們來看一個 SQL
SELECT * FROM t_student WHERE age = 20 OR age <> 20;
咋一看,這不就是查詢表中所有記錄嗎? 咱們來看下實際結果
yzb 沒查出來,這是爲何了?咱們來分析下,yzb 的 age 是 NULL,那麼這條記錄的判斷步驟以下
-- 1. 約翰年齡是 NULL (未知的 NULL !) SELECT * FROM t_student WHERE age = NULL OR age <> NULL; -- 2. 對 NULL 使用比較謂詞後,結果爲unknown SELECT * FROM t_student WHERE unknown OR unknown; -- 3.unknown OR unknown 的結果是unknown (參考三值邏輯的邏輯值表) SELECT * FROM t_student WHERE unknown;
SQL 語句的查詢結果裏只有判斷結果爲 true 的行。要想讓 yzb 出如今結果裏,須要添加下面這樣的 「第 3 個條件」
-- 添加 3 個條件:年齡是20 歲,或者不是20 歲,或者年齡未知 SELECT * FROM t_student WHERE age = 20 OR age <> 20 OR age IS NULL;
CASE 表達式和 NULL
簡單 CASE 表達式以下
CASE col_1 WHEN = 1 THEN 'o' WHEN NULL THEN 'x' END
這個 CASE 表達式必定不會返回 ×。這是由於,第二個 WHEN 子句是 col_1 = NULL 的縮寫形式。正如咱們所知,這個式子的邏輯值永遠是 unknown ,並且 CASE 表達式的判斷方法與 WHERE 子句同樣,只承認邏輯值爲 true 的條件。正確的寫法是像下面這樣使用搜索 CASE 表達式
CASE WHEN col_1 = 1 THEN 'o' WHEN col_1 IS NULL THEN 'x' END
咱們在對 SQL 語句進行性能優化時,常常用到的一個技巧是將 IN 改寫成 EXISTS ,這是等價改寫,並無什麼問題。可是,將 NOT IN 改寫成 NOT EXISTS 時,結果未必同樣。
咱們來看個例子,咱們有以下兩張表:t_student_A 和 t_student_B,分別表示 A 班學生與 B 班學生
DROP TABLE IF EXISTS t_student_A; CREATE TABLE t_student_A ( id INT(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', name VARCHAR(50) NOT NULL COMMENT '名稱', age INT(3) COMMENT '年齡', city VARCHAR(50) NOT NULL COMMENT '城市', remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '備註', primary key(id) ) COMMENT '學生信息'; INSERT INTO t_student_A(name, age, city) VALUE ('zhangsan', 25,'深圳市'),('wangwu', 60, '廣州市'), ('bruce', 32, '北京市'),('yzb', NULL, '深圳市'), ('boss', 43, '深圳市'); DROP TABLE IF EXISTS t_student_B; CREATE TABLE t_student_B ( id INT(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', name VARCHAR(50) NOT NULL COMMENT '名稱', age INT(3) COMMENT '年齡', city VARCHAR(50) NOT NULL COMMENT '城市', remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '備註', primary key(id) ) COMMENT '學生信息'; INSERT INTO t_student_B(name, age, city) VALUE ('馬化騰', 45, '深圳市'),('馬三', 25, '深圳市'), ('馬雲', 43, '杭州市'),('李彥宏', 41, '深圳市'), ('年輕人', 25, '深圳市'); SELECT * FROM t_student_A; SELECT * FROM t_student_B;
需求:查詢與 A 班住在深圳的學生年齡不一樣的 B 班學生,也就說查詢出 :馬化騰 和 李彥宏,這個 SQL 該如何寫,像這樣?
-- 查詢與 A 班住在深圳的學生年齡不一樣的 B 班學生 ? SELECT * FROM t_student_B WHERE age NOT IN ( SELECT age FROM t_student_A WHERE city = '深圳市' );
咱們來看下執行結果
咱們發現結果是空,查詢不到任何數據,這是爲何了 ?這裏 NULL 又開始做怪了,咱們一步一步來看看究竟發生了什麼
-- 1. 執行子查詢,獲取年齡列表 SELECT * FROM t_student WHERE age NOT IN(43, NULL, 25); -- 2. 用NOT 和IN 等價改寫NOT IN SELECT * FROM t_student WHERE NOT age IN (43, NULL, 25); -- 3. 用OR 等價改寫謂詞IN SELECT * FROM t_student WHERE NOT ( (age = 43) OR (age = NULL) OR (age = 25) ); -- 4. 使用德· 摩根定律等價改寫 SELECT * FROM t_student WHERE NOT (age = 43) AND NOT(age = NULL) AND NOT (age = 25); -- 5. 用<> 等價改寫 NOT 和 = SELECT * FROM t_student WHERE (age <> 43) AND (age <> NULL) AND (age <> 25); -- 6. 對NULL 使用<> 後,結果爲 unknown SELECT * FROM t_student WHERE (age <> 43) AND unknown AND (age <> 25); -- 7.若是 AND 運算裏包含 unknown,則結果不爲true(參考三值邏輯的邏輯值表) SELECT * FROM t_student WHERE false 或 unknown;
能夠看出,在進行了一系列的轉換後,沒有一條記錄在 WHERE 子句裏被判斷爲 true 。也就是說,若是 NOT IN 子查詢中用到的表裏被選擇的列中存在 NULL ,則 SQL 語句總體的查詢結果永遠是空。這是很可怕的現象!
爲了獲得正確的結果,咱們須要使用 EXISTS 謂詞
-- 正確的SQL 語句:馬化騰和李彥宏將被查詢到 SELECT * FROM t_student_B B WHERE NOT EXISTS ( SELECT * FROM t_student_A A WHERE B.age = A.age AND A.city = '深圳市' );
執行結果以下
一樣地,咱們再來一步一步地看看這段 SQL 是如何處理年齡爲 NULL 的行的
-- 1. 在子查詢裏和 NULL 進行比較運算,此時 A.age 是 NULL SELECT * FROM t_student_B B WHERE NOT EXISTS ( SELECT * FROM t_student_A A WHERE B.age = NULL AND A.city = '深圳市' ); -- 2. 對NULL 使用「=」後,結果爲 unknown SELECT * FROM t_student_B B WHERE NOT EXISTS ( SELECT * FROM t_student_A A WHERE unknown AND A.city = '深圳市' ); -- 3. 若是AND 運算裏包含 unknown,結果不會是true SELECT * FROM t_student_B B WHERE NOT EXISTS ( SELECT * FROM t_student_A A WHERE false 或 unknown ); -- 4. 子查詢沒有返回結果,所以相反地,NOT EXISTS 爲 true SELECT * FROM t_student_B B WHERE true;
也就是說,yzb 被做爲 「與任何人的年齡都不一樣的人」 來處理了。EXISTS 只會返回 true 或者false,永遠不會返回 unknown。所以就有了 IN 和 EXISTS 能夠互相替換使用,而 NOT IN和 NOT EXISTS 卻不能夠互相替換的混亂現象。
還有一些其餘的陷阱,好比:限定謂詞和 NULL、限定謂詞和極值函數不是等價的、聚合函數和 NULL 等等。
一、NULL 用於表示缺失的值或遺漏的未知數據,不是某種具體類型的值,不能對其使用謂詞
二、對 NULL 使用謂詞後的結果是 unknown,unknown 參與到邏輯運算時,SQL 的運行會和預想的不同
三、 IS NULL 整個是一個謂詞,而不是:IS 是謂詞,NULL 是值;相似的還有 IS TRUE、IS FALSE
四、要想解決 NULL 帶來的各類問題,最佳方法應該是往表裏添加 NOT NULL 約束來盡力排除 NULL
個人項目中有個硬性規定:全部字段必須是 NOT NULL,建表的時候就加上此約束
《SQL進階教程》
https://gitee.com/youzhibing/tools/blob/master/NavicatforMySQL.rar