學而不思則罔,趁著假期好好總結下SQL的程式設計知識。
掌握SQL,首先有兩個知識點要明確,要貫穿在我們整個學習SQL的過程中。
SQL 不同於之前學習的面向過程、面向程式語言,SQL是一門面向集合的程式語言。
面向集合程式語言的特性:
1、三值邏輯判斷(TRUE&FALSE&UNKNOWN)
在我們以往我們所接觸的程式語言都包含布林型別,其中只要有TRUE和FALSE兩個值,這種邏輯體系被稱為二值邏輯。
但是在SQL語言中,除此之外還有第三個值UNKNOWN,這種邏輯體系被稱為三值邏輯,三值邏輯的出現是因為在SQL中存在NULL值。
使用要點:
1)NULL值和任何值作比較都是UNKNOWN
1=NULL 、 2>NULL、3<NULL 、4<>NULL、NULL=NULL 結果都為UNKNOWN
2)三值邏輯真值表
其中淺藍色部分是三值邏輯特有的判斷
總結一下就是:在and運算中 FALSE>UNKNOWN>TRUE
在or運算中 TRUE>UNKNOWN>FALSE
3)另外三值邏輯造成的問題就是排中律不存在
例如:在我們正常認知中假設有一群學生,他們年齡肯定是20歲,或者不是20歲,二者必居其一。
那麼在我們SQL查詢邏輯就應為:
--查詢年齡是20歲或者不是20歲的學生
select * from students where age = 20 or age <> 20;
那麼顯然在我們SQL查詢中 如果學生年齡中存在NULL值,上述查詢是無法通過將全集查出的。
那麼要完全查詢出全集則應該為
/*查詢全集*/
select * from students where age = 20 or age <> 20 or age is NULL;
4)NOT IN 和 NOT EXISTS是不等價的
首先說明 在SQL查詢中 EXISTS總是返回TRUE或FALSE,而 IN 則會返回 TRUE,FALSE還有對於 NULL 值會返回 UNKNOWN。但在過濾器中對 UNKNOWN和FALSE處理方式是相同的,因此在使用EXISTS和IN二者是可以相互替換的。
下面我們將對下面案例進行分析,來探究NOT IN 和 NOT EXISTS的區別
ClassA ClassB
現在我們要查詢 “與B班住在東京且年齡不同的A班同學”
那麼在 NOT IN 的SQL查詢應為
--與B班住在東京的學生年齡不同的A班學生
SELECT * FROM ClassA WHERE age NOT IN (SELECT age FROM ClassB WHERE city = '東京');
通過改寫得
--與B班住在東京的學生年齡不同的A班學生
SELECT * FROM ClassA WHERE age NOT IN (22,23,NULL) --SQL處理1:
SELECT * FROM ClassA WHERE NOT (age = 22) or (age = 23) or (age = NULL)
--SQL處理2:
SELECT * FROM ClassA WHERE NOT (TRUE or UNKNOWN)
--通過三值邏輯得
=>SELECT * FROM ClassB WHERE UNKONOWN
則結果集為空
我們再看看NOT EXISTS 如何處理
--與B班住在東京的學生年齡不同的A班學生
SELECT * FROM ClassA a WHERE NOT EXISTS (SELECT age FROM ClassB b WHERE a.age = b.age AND b.city = '東京')
通過改寫得
--與B班住在東京的學生年齡不同的A班學生
SELECT * FROM ClassA a WHERE NOT EXISTS (SELECT age FROM ClassB b WHERE a.age = NULL AND b.city = '東京'); --SQL處理1:
SELECT * FROM ClassA a WHERE NOT EXISTS (SELECT age FROM ClassB b WHERE UNKNOWN AND TRUE或者FALSE) --SQL處理2:
--子查詢不會返回結果 則父查詢返回所有結果集
=> SELECT * FROM ClassA WHERE TRUE
--最後結果集會出現 '拉里' 和 '伯傑' 兩條記錄
所以我們可以得出結論 當我們篩選的集合中存在 NULL 值時,在使用 NOT IN 查詢時總是得到空集,而在使用 NOT EXISTS 總是返回條件匹配的所有結果集。
5)限定謂詞和極值函式
SQL當中存在兩個限定謂詞ALL、ANY,由於ANY與IN是等價的,我們這裡就說明一下ALL的用法。
當使用ALL時等價 讓每個條件進行AND連線
例如 我們需要計算年齡小於所有輸入列表時:
ALL的使用:
age < ALL (11,22,NULL) => (age < 11) and (age < 22) and (age < NULL) => 結果中存在UNKNOWN 那麼結果集為空
極值函式的使用:
age < MIN(11,22,NULL) => age < 11 因為極值函式會自動忽略掉 NULL 值
而如果輸入列表為一個空集時:
ALL 會返回查詢資料表的所有記錄,而極值函式則會返回NULL。
整個三值邏輯是基於謂詞而來的。
那麼謂詞是什麼?答:謂詞是一種特殊的函式,返回值都是真值(true、false或者unknown)
謂詞邏輯的出現提供了謂詞來判斷命題的真假。在關係資料庫裡,表裡每一行資料可以看作是一個命題。
Tbl_A
例如:按照我們之前的理解表裡第一行資料是一條記錄他有對應的是三個屬性值,而按照邏輯謂詞的觀點為 田中的性別是男,而且年齡是28歲。
謂詞的出現是具有劃時代意義的,原因就在於為命題分析提供了函式式的方法。
5)實體的階層
同樣是謂詞,但是與=、BETWEEN等相比,EXISTS的用法還是大不相同的。概括來說,區別在於“謂詞的引數可以取什麼值”。=、Between 我們只能使用單一值而 Exists 的引數我們取的是行資料的集合。
從上面的圖表我們可以知道,EXISTS的特殊性在於輸入值的階數(輸出值和其他謂詞一樣,都是真值)。謂詞邏輯中,根據輸入值的階數對謂詞進行分類。=或者BETWEEEN等輸入值為一行的謂詞叫作“一階謂詞”,
而像EXISTS這樣輸入值為行的集合的謂詞叫作“二階謂詞”。階(order)是用來區分集合或謂詞的階數的概念。
三階謂詞=輸入值為“集合的集合”的謂詞四階謂詞=輸入值為“集合的集合的集合”的謂詞……
SQL中採用的是狹義的“一階謂詞邏輯”,這是因為SQL裡的EXISTS謂詞最高只能接受一階的實體作為引數。如果想要支援二階、三階等更高階的實體,SQL必須提供相應的支援。理論上這也是可以做到的,只是目前還沒有實現。
6)全稱量化和存在量化
謂詞邏輯中有量詞(限量詞、數量詞)這類特殊的謂詞。我們可以用它們來表達一些這樣的命題:“所有的x都滿足條件P”或者“存在(至少一個)滿足條件P的x”。前者稱為“全稱量詞”,後者稱為“存在量詞”,
分別記作∀、∃。這兩個符號看起來很奇怪。其實,全稱量詞的符號其實是將字母A上下顛倒而形成的,存在量詞則是將字母E左右顛倒而形成的。“對於所有的x, ……”的英語是“for All x,…”,而“存在滿足……的x”的英語是“there Exists x that…”,
這就是這兩個符號的由來。
SQL中的EXISTS謂詞實現了謂詞邏輯中的存在量詞卻不存在全稱量詞。不過二者是可以相互轉換的,有如下定義:
∀ xPx = ¬ ∃ x¬P(所有的x都滿足條件P=不存在不滿足條件P的x)
∃ xPx = ¬ ∀ x¬Px(存在x滿足條件P=並非所有的x都不滿足條件P)
例1:查詢“沒有參加某次會議的人”
/*法一:查詢沒有參加某次會議的人*/
SELECT
temp.`meeting`,
temp.`person`
FROM
(SELECT
* FROM
(SELECT DISTINCT person FROM Meetings)a,(SELECT DISTINCT meeting FROM Meetings)b)temp
LEFT JOIN Meetings c ON temp.meeting = c.meeting AND temp.person = c.person
WHERE c.person IS NULL /*法二:查詢沒有參加某次會議的人*/
SELECT DISTINCT
a.`meeting`,b.person
FROM Meetings a,Meetings b
WHERE NOT EXISTS
(
SELECT * FROM Meetings c WHERE a.`meeting` = c.meeting AND c.`person` = b.person
)
例2:全稱量化
1.“肯定⇔雙重否定”之間的轉換 請查詢出“所有科目分數都在50分以上的學生”
查詢“所有科目分數都在50分以上的學生” 轉換 查詢 “沒有一個科目分數不滿50分”
/*查詢所有科目分數50分以上*/
SELECT DISTINCT student_id
FROM TestScores TS1
WHERE NOT EXISTS
(
SELECT * FROM TestScores TS2
WHERE TS2.student_id = TS1.student_id
AND TS2.score < 50)
接下來我們把條件改得複雜一些再試試。
請查詢“某個學生的所有行資料中,如果科目是數學,則分數在80分以上;如果科目是語文,則分數在50分以上。”
/* 全稱量化(1):習慣“肯定<=>雙重否定”之間的轉換 */
SELECT student_id
FROM TestScores TS1
WHERE subject IN ('數學', '語文')
AND NOT EXISTS
(SELECT *
FROM TestScores TS2
WHERE TS2.student_id = TS1.student_id
AND 1 = CASE WHEN subject = '數學' AND score < 80 THEN 1
WHEN subject = '語文' AND score < 50 THEN 1
ELSE 0 END)
GROUP BY student_id
HAVING COUNT(*) = 2; /* 必須兩門科目都有分數 */
例3:查詢出哪些專案已經完成到了工程1
having子句解題:
--having 子句
/*查詢哪些專案已經完成到了工程1*/
SELECT
a.project_id
FROM Projects a
GROUP BY a.project_id
HAVING COUNT(*) = SUM(CASE WHEN step_nbr <= 1 AND STATUS = '完成' THEN 1
WHEN step_nbr >1 AND STATUS = '等待' THEN 1
ELSE 0 END)
exists子句解題:
/*查詢哪些專案已經完成到了工程1*/
/*--->查詢不存在哪些專案沒有完成工程1以及完成過了工程1*/
法一:
SELECT
*
FROM Projects a
WHERE NOT EXISTS (
SELECT STATUS
FROM Projects b
WHERE a.project_id = b.project_id
AND STATUS <> CASE WHEN step_nbr <= 1 THEN '完成' ELSE '等待' END
)
法二:
SELECT
*
FROM Projects b
WHERE NOT EXISTS(
SELECT
*
FROM
Projects a WHERE a.project_id = b.project_id AND(
(step_nbr > 1 AND STATUS = '完成') OR (step_nbr = 1 AND STATUS = '等待'))
)
例四:查詢現在能出勤的隊伍
having子句:
/*查詢可以出勤的隊伍 having*/
SELECT
a.`team_id`
FROM
Teams a
GROUP BY a.`team_id`
HAVING COUNT(*) = (SELECT COUNT(*) FROM Teams b WHERE a.`team_id` = b.`team_id` AND b.`status` = '待命')
exists 子句:
/*查詢可以出勤的隊伍 全稱量化*/
/*隊伍中不存在不是待命的隊員*/
SELECT
*
FROM
Teams a WHERE NOT EXISTS (
SELECT * FROM Teams b WHERE a.`team_id` = b.`team_id` AND b.`status` <> '待命'
)
例五:查詢存在重複材料的生產地
having子句:
/*查詢存在重複材料的生產地 having*/
SELECT
a.center
FROM
Materials a
GROUP BY a.center
HAVING COUNT(*) <> COUNT(DISTINCT a.material)
exists子句:
/*存在重複的生產地*/
SELECT
*
FROM
Materials a
WHERE EXISTS (
SELECT * FROM Materials b WHERE a.center = b.`center` AND a.receive_date <> b.`receive_date` AND a.material = b.`material`
)
例六:查詢出75%以上的學生分數都在80以上的班級。
/*請查詢出75%以上的學生分數都在80分以上的班級*/
SELECT b.class FROM TestResults b
GROUP BY b.class
HAVING SUM(CASE WHEN score>=80 THEN 1 ELSE 0 END)/COUNT(*) > 0.75
例7:找出連續為3個空位的組合
/*全是被未預訂的座位*/
/*--->不存在不是被未預訂的座位*/
SELECT
*
FROM Seats a,Seats b
WHERE b.seat = a.seat + 2
AND NOT EXISTS (
SELECT *
FROM Seats c
WHERE c.`seat` BETWEEN a.seat AND b.seat AND c.`status` <> '未預訂'
)