成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

搞清這些陷阱,NULL和三值邏輯再也不作妖

運維 數據庫運維
NULL 用于表示缺失的值或遺漏的未知數據,不是某種具體類型的值。數據表中的 NULL 值表示該值所處的字段為空,值為 NULL 的字段沒有值,尤其要明白的是:NULL 值與 0 或者空字符串是不同的。

 [[281624]]

NULL

NULL 用于表示缺失的值或遺漏的未知數據,不是某種具體類型的值。數據表中的 NULL 值表示該值所處的字段為空,值為 NULL 的字段沒有值,尤其要明白的是:NULL 值與 0 或者空字符串是不同的。

兩種NULL

這種說法大家可能會覺得很奇怪,因為 SQL 里只存在一種 NULL 。然而在討論 NULL 時,我們一般都會將它分成兩種類型來思考:“未知”(unknown)和“不適用”(not applicable,inapplicable)。

以“不知道戴墨鏡的人眼睛是什么顏色”這種情況為例,這個人的眼睛肯定是有顏色的,但是如果他不摘掉眼鏡,別人就不知道他的眼睛是什么顏色。這就叫作未知。

而“不知道冰箱的眼睛是什么顏色”則屬于“不適用”。因為冰箱根本就沒有眼睛,所以“眼睛的顏色”這一屬性并不適用于冰箱。“冰箱的眼睛的顏色”這種說法和“圓的體積”“男性的分娩次數”一樣,都是沒有意義的。

平時,我們習慣了說“不知道”,但是“不知道”也分很多種。“不適用”這種情況下的 NULL ,在語義上更接近于“無意義”,而不是“不確定”。

這里總結一下:“未知”指的是“雖然現在不知道,但加上某些條件后就可以知道”;而“不適用”指的是“無論怎么努力都無法知道”。

關系模型的發明者 E.F. Codd 最先給出了這種分類。下圖是他對“丟失的信息”的分類。

“IS NULL”而非“= NULL”?

為什么必須寫成“IS NULL”,而不是“= NULL”?我相信不少人有這樣的困惑吧,尤其是相信剛學 SQL 的小伙伴。我們來看個具體的案例,假設我們有如下表以及數據:

  1. DROP TABLE IF EXISTS t_sample_null; 
  2. CREATE TABLE t_sample_null ( 
  3.     id INT(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵'
  4.     name VARCHAR(50) NOT NULL COMMENT '名稱'
  5.     remark VARCHAR(500) COMMENT '備注'
  6.     primary key(id) 
  7. ) COMMENT 'NULL樣例'
  8.  
  9. INSERT INTO t_sample_null(name, remark) 
  10. VALUES('zhangsan''張三'),('李四'NULL); 

我們要查詢備注為 NULL 的記錄(為 NULL 這種叫法本身是不對的,只是我們日常中已經叫習慣了,具體往下看),怎么查,很多新手會寫出這樣的 SQL:

  1. -- SQL 不報錯,但查不出結果 
  2. SELECT * FROM t_sample_null WHERE remark = NULL

 

執行時不報錯,但是查不出我們想要的結果, 這是為什么 ?這個問題我們先放著,我們往下看。

三值邏輯

這個三值邏輯不是三目運算,指的是三個邏輯值,有人可能有疑問了,邏輯值不是只有真(true)和假(false)嗎,哪來的第三個?

說這話時我們需要注意所處的環境,在主流的編程語言中(C、JAVA、Python、JS等)中,邏輯值確實只有 2 個,但在 SQL 中卻存在第三個邏輯值:unknown。這有點類似于我們平時所說的:對、錯、不知道。

邏輯值 unknown 和作為 NULL 的一種的 UNKNOWN (未知)是不同的東西。前者是明確的布爾型的邏輯值,后者既不是值也不是變量。

為了便于區分,前者采用小寫字母 unknown ,后者用大寫字母 UNKNOWN 來表示。為了讓大家理解兩者的不同,我們來看一個 x=x 這樣的簡單等式。x 是邏輯值 unknown 時,x=x 被判斷為 true ,而 x 是 UNKNOWN 時被判斷為 unknown 。

  1. -- 這個是明確的邏輯值的比較 
  2. unknown = unknown → true 
  3.  
  4. -- 這個相當于NULL = NULL 
  5. 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 )。

  1. -- 假設 a = 2, b = 5, c = NULL,下列表達式的邏輯值如下 
  2.  
  3. a < b AND b > c  → unknown 
  4. a > b OR b < c   → unknown 
  5. a < b OR b < c   → true 
  6. NOT (b <> c)     → unknown 

“IS NULL” 而非 “= NULL”

我們再回到問題:為什么必須寫成“IS NULL”,而不是“= NULL”?

對 NULL 使用比較謂詞后得到的結果總是 unknown 。而查詢結果只會包含 WHERE 子句里的判斷結果為 true 的行,不會包含判斷結果為 false 和 unknown 的行。不只是等號,對 NULL 使用其他比較謂詞,結果也都是一樣的。

所以無論 remark 是不是 NULL ,比較結果都是 unknown ,那么永遠沒有結果返回。以下的式子都會被判為 unknown:

-- 以下的式子都會被判為 unknown

  1. -- 以下的式子都會被判為 unknown 
  2. NULL 
  3. NULL 
  4. NULL 
  5. <> NULL 
  6. 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 這樣也許更合適。

溫柔的陷阱

比較謂詞和 NULL

排中律不成立。排中律指同一個思維過程中,兩個相互矛盾的思想不能同假,必有一真,即“要么A要么非A”。

假設我們有學生表:t_student:

  1. DROP TABLE IF EXISTS t_student; 
  2. CREATE TABLE t_student ( 
  3.     id INT(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵'
  4.     name VARCHAR(50) NOT NULL COMMENT '名稱'
  5.     age INT(3) COMMENT '年齡'
  6.     remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '備注'
  7.     primary key(id) 
  8. ) COMMENT '學生信息'
  9.  
  10. INSERT INTO t_student(name, age) 
  11. VALUE('zhangsan', 25),('wangwu', 60),('bruce', 32),('yzb'NULL),('boss', 18); 
  12.  
  13. SELECT * FROM t_student; 

表中數據 yzb 的 age 是 NULL,也就是說 yzb 的年齡未知。在現實世界里,yzb 是 20 歲,或者不是 20 歲,二者必居其一,這毫無疑問是一個真命題。那么在 SQL 的世界里了,排中律還適用嗎? 我們來看一個 SQL :

  1. SELECT * FROM t_student 
  2. WHERE age = 20 OR age <> 20; 

咋一看,這不就是查詢表中全部記錄嗎?我們來看下實際結果:

 

yzb 沒查出來,這是為什么?我們來分析下,yzb 的 age 是 NULL,那么這條記錄的判斷步驟如下:

  1. -- 1. 約翰年齡是 NULL (未知的 NULL !) 
  2. SELECT * 
  3. FROM t_student 
  4. WHERE age = NULL 
  5. OR age <> NULL
  6.  
  7. -- 2. 對 NULL 使用比較謂詞后,結果為unknown 
  8. SELECT * 
  9. FROM t_student 
  10. WHERE unknown 
  11. OR unknown; 
  12.  
  13. -- 3.unknown OR unknown 的結果是unknown (參考三值邏輯的邏輯值表) 
  14. SELECT * 
  15. FROM t_student 
  16. WHERE unknown; 

SQL 語句的查詢結果里只有判斷結果為 true 的行。要想讓 yzb 出現在結果里,需要添加下面這樣的 “第 3 個條件”:

-- 添加 3 個條件:年齡是20 歲,或者不是20 歲,或者年齡未知。

  1. -- 添加 3 個條件:年齡是20 歲,或者不是20 歲,或者年齡未知 
  2. SELECT * FROM t_student 
  3. WHERE age = 20  
  4.     OR age <> 20 
  5.     OR age IS NULL

CASE 表達式和 NULL。簡單 CASE 表達式如下:

  1. CASE col_1 
  2.     WHEN = 1 THEN 'o' 
  3.     WHEN NULL THEN 'x' 
  4. END 

這個 CASE 表達式一定不會返回 ×。這是因為,第二個 WHEN 子句是 col_1 = NULL 的縮寫形式。正如我們所知,這個式子的邏輯值永遠是 unknown ,而且 CASE 表達式的判斷方法與 WHERE 子句一樣,只認可邏輯值為 true 的條件。正確的寫法是像下面這樣使用搜索 CASE 表達式:

  1. CASE WHEN col_1 = 1 THEN 'o' 
  2.     WHEN col_1 IS NULL THEN 'x' 
  3. END 

NOT IN 和 NOT EXISTS 并非等價

我們在對 SQL 語句進行性能優化時,經常用到的一個技巧是將 IN 改寫成 EXISTS ,這是等價改寫,并沒有什么問題。但是,將 NOT IN 改寫成 NOT EXISTS 時,結果未必一樣。

我們來看個例子,我們有如下兩張表:t_student_A 和 t_student_B,分別表示 A 班學生與 B 班學生。

  1. DROP TABLE IF EXISTS t_student_A; 
  2. CREATE TABLE t_student_A ( 
  3.     id INT(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵'
  4.     name VARCHAR(50) NOT NULL COMMENT '名稱'
  5.     age INT(3) COMMENT '年齡'
  6.     city VARCHAR(50) NOT NULL COMMENT '城市'
  7.     remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '備注'
  8.     primary key(id) 
  9. ) COMMENT '學生信息'
  10.  
  11. INSERT INTO t_student_A(name, age, city) 
  12. VALUE 
  13. ('zhangsan', 25,'深圳市'),('wangwu', 60, '廣州市'), 
  14. ('bruce', 32, '北京市'),('yzb'NULL'深圳市'), 
  15. ('boss', 43, '深圳市'); 
  16.  
  17. DROP TABLE IF EXISTS t_student_B; 
  18. CREATE TABLE t_student_B ( 
  19.     id INT(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵'
  20.     name VARCHAR(50) NOT NULL COMMENT '名稱'
  21.     age INT(3) COMMENT '年齡'
  22.     city VARCHAR(50) NOT NULL COMMENT '城市'
  23.     remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '備注'
  24.     primary key(id) 
  25. ) COMMENT '學生信息'
  26.  
  27. INSERT INTO t_student_B(name, age, city) 
  28. VALUE 
  29. ('馬化騰', 45, '深圳市'),('馬三', 25, '深圳市'), 
  30. ('馬云', 43, '杭州市'),('李彥宏', 41, '深圳市'), 
  31. ('年輕人', 25, '深圳市'); 
  32.  
  33. SELECT * FROM t_student_A; 
  34. SELECT * FROM t_student_B; 

需求:查詢與 A 班住在深圳的學生年齡不同的 B 班學生,也就說查詢出 :馬化騰 和 李彥宏,這個 SQL 該如何寫,像這樣?

  1. -- 查詢與 A  班住在深圳的學生年齡不同的 B 班學生 ? 
  2. SELECT * FROM t_student_B 
  3. WHERE age NOT IN ( 
  4.     SELECT age FROM t_student_A  
  5.     WHERE city = '深圳市' 
  6. ); 

我們來看下執行結果:

 

我們發現結果是空,查詢不到任何數據,這是為什么 ?這里 NULL 又開始作怪了,我們一步一步來看看究竟發生了什么。

  1. -- 1. 執行子查詢,獲取年齡列表 
  2. SELECT * FROM t_student 
  3. WHERE age NOT IN(43, NULL, 25); 
  4.  
  5. -- 2. 用NOT 和IN 等價改寫NOT IN 
  6. SELECT * FROM t_student 
  7. WHERE NOT age IN (43, NULL, 25); 
  8.  
  9. -- 3. 用OR 等價改寫謂詞IN 
  10. SELECT * FROM t_student 
  11. WHERE NOT ( (age = 43) OR (age = NULLOR (age = 25) ); 
  12.  
  13. -- 4. 使用德· 摩根定律等價改寫 
  14. SELECT * FROM t_student 
  15. WHERE NOT (age = 43) AND NOT(age = NULLAND NOT (age = 25); 
  16.  
  17. -- 5. 用<> 等價改寫 NOT 和 = 
  18. SELECT * FROM t_student 
  19. WHERE (age <> 43) AND (age <> NULLAND (age <> 25); 
  20.  
  21. -- 6. 對NULL 使用<> 后,結果為 unknown 
  22. SELECT * FROM t_student 
  23. WHERE (age <> 43) AND unknown AND (age <> 25); 
  24.  
  25. -- 7.如果 AND 運算里包含 unknown,則結果不為true(參考三值邏輯的邏輯值表) 
  26. SELECT * FROM t_student 
  27. WHERE false 或 unknown; 

可以看出,在進行了一系列的轉換后,沒有一條記錄在 WHERE 子句里被判斷為 true 。也就是說,如果 NOT IN 子查詢中用到的表里被選擇的列中存在 NULL ,則 SQL 語句整體的查詢結果永遠是空。這是很可怕的現象!

為了得到正確的結果,我們需要使用 EXISTS 謂詞。

  1. -- 正確的SQL 語句:馬化騰和李彥宏將被查詢到 
  2. SELECT * FROM t_student_B B 
  3. WHERE NOT EXISTS (  
  4.     SELECT * FROM t_student_A A 
  5.     WHERE B.age = A.age 
  6.     AND A.city = '深圳市'  
  7. ); 

執行結果如下:

 

同樣地,我們再來一步一步地看看這段 SQL 是如何處理年齡為 NULL 的行的:

  1. -- 1. 在子查詢里和 NULL 進行比較運算,此時 A.age 是 NULL 
  2. SELECT * FROM t_student_B B 
  3. WHERE NOT EXISTS (  
  4.     SELECT * FROM t_student_A A 
  5.     WHERE B.age = NULL 
  6.     AND A.city = '深圳市'  
  7. ); 
  8.  
  9. -- 2. 對NULL 使用“=”后,結果為 unknown 
  10. SELECT * FROM t_student_B B 
  11. WHERE NOT EXISTS (  
  12.     SELECT * FROM t_student_A A 
  13.     WHERE unknown 
  14.     AND A.city = '深圳市'  
  15. ); 
  16.  
  17. -- 3. 如果AND 運算里包含 unknown,結果不會是true 
  18. SELECT * FROM t_student_B B 
  19. WHERE NOT EXISTS (  
  20.     SELECT * FROM t_student_A A 
  21.     WHERE false 或 unknown 
  22. ); 
  23.  
  24. -- 4. 子查詢沒有返回結果,因此相反地,NOT EXISTS 為 true 
  25. SELECT * FROM t_student_B B 
  26. WHERE true

也就是說,yzb 被作為 “與任何人的年齡都不同的人” 來處理了。EXISTS 只會返回 true 或者false,永遠不會返回 unknown。因此就有了 IN 和 EXISTS 可以互相替換使用,而 NOT IN和 NOT EXISTS 卻不可以互相替換的混亂現象。

還有一些其他的陷阱,比如:限定謂詞和 NULL、限定謂詞和極值函數不是等價的、聚合函數和 NULL 等等。

總結

1、NULL 用于表示缺失的值或遺漏的未知數據,不是某種具體類型的值,不能對其使用謂詞。

2、對 NULL 使用謂詞后的結果是 unknown,unknown 參與到邏輯運算時,SQL 的運行會和預想的不一樣。

3、 IS NULL 整個是一個謂詞,而不是:IS 是謂詞,NULL 是值;類似的還有 IS TRUE、IS FALSE。

4、要想解決 NULL 帶來的各種問題,最佳方法應該是往表里添加 NOT NULL 約束來盡力排除 NULL。

我的項目中有個硬性規定:所有字段必須是 NOT NULL,建表的時候就加上此約束。

 

責任編輯:武曉燕 來源: DBAplus社群
相關推薦

2022-09-13 08:33:05

SQLNULL三值邏輯

2020-04-30 09:19:56

Docker容器虛擬機

2020-01-21 21:15:16

WiFi網絡WiFi6

2013-10-09 13:25:42

產品經理產品

2021-12-21 09:05:46

命令Linux敲錯

2018-02-07 08:01:20

windows微軟windows 10

2024-04-15 00:08:00

MySQLInnoDB數據庫

2025-05-28 03:20:00

布爾值編程True

2024-02-26 00:00:00

Docker容器

2015-05-29 09:01:48

2021-12-01 17:24:11

編程學習網站程序員

2021-06-08 07:48:26

數據 Python開發

2017-01-23 08:41:43

云計算

2020-06-15 08:03:17

大文件OOM內存

2020-03-30 21:46:26

SQL數據分析數據

2021-08-12 11:05:07

C++語言內存泄露

2021-03-26 15:18:11

代碼工具Mockoon

2020-04-20 15:00:22

DevOps工具代碼

2023-07-14 21:34:40

JVM上下線線程

2014-07-18 15:54:04

goTenna:隨身無
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品久久久久久久久久 | 狠狠的干 | 2021天天干夜夜爽 | 亚洲成人免费网址 | 国产乱肥老妇国产一区二 | 国产精品高清一区二区三区 | 亚洲午夜精品久久久久久app | 欧美一区二区 | 日韩精品一区二区三区在线播放 | 欧美成人黄色小说 | 成人av一区二区亚洲精 | 怡红院免费的全部视频 | 欧美jizzhd精品欧美巨大免费 | 天天干狠狠操 | 亚洲一区二区电影网 | 成人一区精品 | 亚洲网站在线观看 | 一本在线 | 婷婷色国产偷v国产偷v小说 | 日韩在线免费播放 | 国产精品精品视频一区二区三区 | 日韩一区二区在线视频 | 久久久久久久久毛片 | 亚洲精品一区二区 | 免费毛片网 | 黄色国产在线播放 | 欧美日韩国产一区二区 | 欧美电影一区 | 黄色免费看 | 欧美视频二区 | 国产伦精品一区二区三区照片91 | 色综合天天天天做夜夜夜夜做 | 国产精品永久免费 | 国产日本精品视频 | 成人av片在线观看 | 日韩黄色av | 在线播放国产一区二区三区 | 拍真实国产伦偷精品 | 欧美日韩国产高清 | 久久久九九九九 | 91成人免费观看 |