本文主講數據查詢中的 嵌套查詢(EXISTS部分)、集合查詢、基於派生表的查詢,歡迎閱讀~
📋簡潔的目錄(暗示按順序看)
一、帶有EXISTS謂詞的子查詢
EXISTS謂詞
- 存在量詞
- 帶有EXISTS謂詞的子查詢不返回任何數據,只產生 邏輯真值“true” 或 邏輯假值“false”
若內層查詢結果非空,則外層的 WHERE子句返回真值
若內層查詢結果爲空,則外層的 WHERE子句返回假值 - 由EXISTS引出的子查詢,其目標列表達式通常都用 * ,因爲帶EXISTS的子查詢只返回真值或假值,給出列名無實際意義
NOT EXISTS謂詞(記住與EXISTS相反就行)
若內層查詢結果非空,則外層的WHERE子句返回假值
若內層查詢結果爲空,則外層的WHERE子句返回真值
🌟來看例子: 查詢所有選修了1號課程的學生姓名:
· 理理思路:
① 選修了1號課程所以涉及SC表,學生姓名涉及Student表
② 在Student中依次取每個元組的Sno值,用此值去檢查SC表
③ 若SC中存在這樣的元組,其Sno值等於此Student.Sno值,並且其Cno= ‘1’,則取出此Student.Sname送入結果表)
(運用相關子查詢)
SELECT Sname
FROM Student
WHERE EXISTS
( SELECT *
FROM SC
WHERE Sno = Student.Sno AND Cno = '1');
ps:🙋這個題目明顯也可以直接用連接查詢解決,那爲什麼還要用EXISTS寫這麼多呢?
採用EXISTS謂詞能更容易理解,而且連接查詢能表達的它都能表達,但是它能表達的連接查詢並不一定能表達,對於這個題來說兩種方式均可,不過EXISTS的應用範圍更廣。
🌟Another one: 查詢沒有選修1號課程的學生姓名:
(因爲剛剛求解過選修1號課程的學生,這裏直接將其否定就可以啦,採用NOT EXISTS謂詞)
SELECT Sname
FROM Student
WHERE NOT EXISTS
( SELECT *
FROM SC
WHERE Sno = Student.Sno AND Cno = '1');
不同形式的查詢間的替換
- 一些帶EXISTS 或 NOT EXISTS謂詞的子查詢不能被其他形式的子查詢等價替換
- 所有帶IN謂詞、比較運算符、ANY和ALL謂詞的子查詢都能用帶EXISTS謂詞的子查詢等價替換
- 總結一下就是: EXISTS比IN、比較運算符、ANY、ALL的應用範圍更廣,在這一層面上來說EXISTS更強一些,而且EXISTS的表達能力更強,比較容易理解(但是! 有時候用EXISTS沒有用其他的方法方便和好用,所以該用什麼得根據情況而定,這裏只是說明EXISTS是能用的,不過不一定是最適合的)
🌟來看例子: 查詢與“劉晨 ”在同一個系學習的學生:
(看過我之前的博客的小夥伴應該對這個例子有印象,之前我是採用 IN謂詞 和 自身連接 來解決的,詳細過程和解釋可康我的上一篇博客吖:SQL Server 數據庫基本操作入門篇【5】)
SELECT Sno, Sname, Sdept
FROM Student S1
WHERE EXISTS
( SELECT *
FROM Student S2
WHERE S2.Sdept = S1.Sdept AND S2.Sname = '劉晨'); /*採用EXISTS謂詞解決,且是一個相關子查詢*/
SELECT Sno, Sname, Sdept
FROM Student
WHERE Sdept IN
( SELECT Sdept
FROM Student
WHERE Sname = '劉晨'); /*採用IN謂詞解決,且是一個不相關子查詢*/
SELECT S1.Sno, S1.Sname, S1.Sdept
FROM Student S1, Student S2
WHERE S1.Sdept = S2.Sdept AND S2.Sname = '劉晨'; /*採用自身連接解決*/
(ps:相關子查詢和不相關子查詢我在上一篇博客中都有介紹和舉例說明,不太清楚的小夥伴可以去康康~)
用 EXISTS / NOT EXISTS 實現全稱量詞
SQL語言中沒有全稱量詞 (For all)
可以把帶有全稱量詞的謂詞轉換爲等價的帶有存在量詞的謂詞:( x )P ( x( P))
(ps: 表示恆等於)
這樣看不太好理解,咱通過例子來搞明白它!
🌟來看例子: 查詢選修了全部課程的學生姓名:
(選修了全部課程可理解爲:沒有一門課程是他沒選修的)
SELECT Sname
FROM Student
WHERE NOT EXISTS
( SELECT *
FROM Course
WHERE NOT EXISTS
( SELECT *
FROM SC
WHERE Sno = Student.Sno AND Cno = Course.Cno
)
);
這裏以學號爲201215121的李勇同學舉例,因爲咱們這裏的NOT EXISTS謂詞解法採用的是相關子查詢,所以執行順序是從外向內:
— — — — — — — — — — — — — — — —
① 從Student表中取學號 201215121
② 進入到第二層,Course的課程號取值 1
③ 帶着取的值進入第三層(這裏的最內層):WHERE Sno = '201215121' AND Cno = '1'
,如圖SC表中是有這個元組的(即查詢結果爲T),因爲是NOT EXISTS 所以 返回 F
④ 回到第二層,Course的課程號取值 2
⑤ 帶着取的值進入第三層:WHERE Sno = '201215121' AND Cno = '2'
,同樣SC表中是有這個元組的,所以 返回 F
⑥ …(如此往復)
⑦ 回到第二層,Course的課程號取值 9
⑧ 帶着取的值進入第三層:WHERE Sno = '201215121' AND Cno = '9'
,同樣SC表中是有這個元組的,所以 返回 F
⑨ 得到第二層的結果:F∨F∨F∨F∨F∨F∨F = F
⑩ 所以得到第一層的結果:T
— — — — — — — — — — — — — — — —
所以得出結論:李勇同學選修了全部課程
以此類推,換下一個學號繼續執行以上步驟,直到把每一個學號都執行完,就得出最終結果
用 EXISTS / NOT EXISTS 實現邏輯蘊涵
SQL語言中沒有蘊涵邏輯運算
可以利用謂詞演算將邏輯蘊涵謂詞等價轉換爲:
p q p∨q
同樣,通過例子來講解
🌟來看例子: 查詢至少選修了學生201215122選修的全部課程的學生號碼:
(這裏假如是李勇至少選修了他的全部課程,則可理解爲:李勇選修的課程裏面包含着他選修的所有課程)
· 思路:
① 用邏輯蘊涵表達:查詢學號爲x的學生,對所有的課程y,只要201215122學生選修了課程y,則x也選修了y(即x的課程裏含有201215122選修的所有課程)
② 形式化表示:
用P表示謂詞 “學生201215122選修了課程y”
用q表示謂詞 “學生x選修了課程y”
則上述查詢爲: (y) p q (對任意的課程y,p都包含於q)
③ 等價變換:(y)p q y(p∧q)(因爲剛剛介紹了,SQL中沒有沒有全稱量詞 ,所以這裏仍然採用存在量詞來表示)
④ 變換後的語義:不存在這樣的課程y,學生201215122選修了y,而學生x沒有選
(這裏數學公式可能不太好理解,直接理解文字表達即可,然後結合下面的例子來強化一下~)
SELECT DISTINCT Sno
FROM SC SCX
WHERE NOT EXISTS
( SELECT *
FROM SC SCY
WHERE SCY.Sno = '201215122'
AND NOT EXISTS
( SELECT *
FROM SC SCZ
WHERE SCZ.Sno = SCX.Sno AND SCZ.Cno = SCY.Cno));
同樣的,因爲咱們這裏的NOT EXISTS謂詞解法採用的是相關子查詢,所以執行順序是從外向內:
— — — — — — — — — — — — — — — —
這裏給SC表取別名爲SCX、SCY、SCZ
① 從SCX表中取學號 201215121
② 進入到第二層,SCY的課程號取值 2(如圖SC表中學號爲201215122的學生選的課程號只有2和3號課)
③ 帶着取的值進入第三層(這裏的最內層):WHERE SCZ.Sno = '201215121' AND SCZ.Cno = '2'
,如圖SCZ表中是有這個元組的(即查詢結果爲T),因爲這裏是NOT EXISTS 所以 返回 F
④ 回到第二層,SCY的課程號取值 3
⑤ 帶着取的值進入第三層:WHERE SCZ.Sno = '201215121' AND SCZ.Cno = '3'
,同樣SCZ表中是有這個元組的,所以 返回 F
⑥ 得到第二層的結果:F∨F = F
⑦ 所以得到第一層的結果:T
— — — — — — — — — — — — — — — —
所以得出結論:201215121選修了學生201215122選修的全部課程
以此類推,換下一個學號繼續執行以上步驟,直到把每一個學號都執行完,就得出最終結果
二、集合查詢
集合操作的種類
並:UNION
交:INTERSECT
差:EXCEPT
· 參加集合操作的各查詢結果的列數必須相同
· 對應項的數據類型必須相同
🌟來看例子: 查詢計算機科學系的學生及年齡不大於19歲的學生:
(這裏是及
所以用的是並:UNION
,求兩個查詢條件下結果的並集)
SELECT *
FROM Student
WHERE Sdept = 'CS'
UNION
SELECT *
FROM Student
WHERE Sage <= 19;
UNION: 將多個查詢結果合併起來時,系統自動去掉重複元組
UNION ALL: 將多個查詢結果合併起來時,保留重複元組
👀可看到上圖中因爲劉晨既滿足第一個查詢條件計算機系,又滿足第二個查詢條件不大於19歲,所以當用的是UNION ALL時,劉晨打印了兩次,該題不需要知道誰兩個條件都滿足,所以用UNION即可
🌟Another one: 查詢選修了課程1或者選修了課程2的學生:
(同樣,這裏的關鍵詞或者
提示我們用並:UNION
求兩個集合的並集)
SELECT Sno
FROM SC
WHERE Cno = '1'
UNION
SELECT Sno
FROM SC
WHERE Cno = '2';
SELECT * FROM SC; /*打印出整個SC表參照*/
🌟Another one: 查詢計算機科學系的學生與年齡不大於19歲的學生的交集:
(嘿嘿,這題說的夠直白了叭,求交集,所以當然用INTERSECT
)
SELECT *
FROM Student
WHERE Sdept = 'CS'
INTERSECT
SELECT *
FROM Student
WHERE Sage <= 19;
該題題意可理解爲:查詢計算機科學系中年齡不大於19歲的學生:
所以可以直接採用單表查詢,很容易的就將其解決(所以,看到題目之後先彆着急寫,可以先思考一下采取哪種方法更簡單~)
SELECT *
FROM Student
WHERE Sdept = 'CS' AND Sage <= 19;
🌟Another one: 查詢既選修了課程1又選修了課程2的學生:
SELECT Sno
FROM SC
WHERE Cno = '1'
INTERSECT
SELECT Sno
FROM SC
WHERE Cno = '2';
當然,該題也可採用嵌套查詢:
SELECT Sno
FROM SC
WHERE Cno = '1' AND Sno IN
( SELECT Sno
FROM SC
WHERE Cno = '2');
🙋誒,那有的小夥伴就會說:“既選了課程1又選了課程2” 那是不是可以直接用單表查詢Cno = '1' AND Cno = '2'
來解決呢?
爲了滿足你的好奇心我在下圖中進行了實驗:
顯然,單表查詢是不行的,這是因爲:WHERE Cno = '1' AND Cno = '2';
條件表達式下,在一張表中,滿足Cno = '1’的條件下查詢出的元組裏Cno是不可能爲’2’的,它滿足條件就已經說明Cno是’1’了,即一個元組中只能有一個Cno,它不可能同時等於兩個值。
而嵌套查詢中IN謂詞的子查詢的結果是Cno='2’的學生的學號,所以對於一個學生來說他的課程號Cno是可以既有1又有2的(即對於一個學生來說,他是可以選多門課程的)。
🌟Another one: 查詢計算機科學系的學生與年齡不大於19歲的學生的差集:
SELECT *
FROM Student
WHERE Sdept = 'CS'
EXCEPT
SELECT *
FROM Student
WHERE Sage <= 19;
這裏實際上就是查詢計算機科學系中年齡大於19歲的學生,解法同剛纔的一個例題,可直接採用單表查詢,AND 來輕鬆解決:
SELECT *
FROM Student
WHERE Sdept = 'CS' AND Sage > 19;
三、基於派生表的查詢
子查詢不僅可以出現在WHERE子句中,
還可以出現在FROM子句中,
這時子查詢生成的臨時派生表成爲主查詢的查詢對象。
🌟來看例子: 找出每個學生超過他自己選修課程平均成績的課程號:
(這個例題在我直接的例子中出現過,當時是採用的比較運算符結合相關子查詢來解決的,具體解釋可參考我的上一篇博客中對相關子查詢的講解部分:SQL Server 數據庫基本操作入門篇【5】)
SELECT Sno, Cno
FROM SC, ( SELECT Sno, Avg(Grade)
FROM SC
GROUP BY Sno)
AS Avg_sc(avg_sno,avg_grade)
WHERE SC.Sno = Avg_sc.avg_sno AND SC.Grade >= Avg_sc.avg_grade
SELECT Sno, Cno
FROM SC x
WHERE Grade >= ( SELECT AVG (Grade)
FROM SC y
WHERE y.Sno = x.Sno); /*採用比較運算符相關子查詢*/
這裏子查詢寫在FROM子句的解法中,Avg_sc表即子查詢產生的臨時派生表,它和SC表共同作爲該主查詢的查詢對象。
子查詢中查詢出的Sno構成Avg_sc表的avg_sno屬性列,聚集函數Avg(Grade) 的結果構成Avg_sc表的avg_grade屬性列。
通過SC.Sno = Avg_sc.avg_sno
將兩表連接上之後,SC.Grade >= Avg_sc.avg_grade
進行比較,得出結果。
如果子查詢中沒有聚集函數,派生表可以不指定屬性列(名),子查詢SELECT子句後面的列名爲其缺省屬性。
(👀這裏的意思就是,如果子查詢中有聚集函數,例如剛剛的例子中Avg(Grade)
,因爲它不是一個屬性名,所以由此產生的派生表中該屬性列沒有名字!!這時就需要給它指定屬性列(名),如上面的Avg_sc(avg_sno,avg_grade)
中的avg_grade
,而 比如這裏的Sno本身就是一個列名(屬性名),所以可以不用指定avg_sno
,用的時候直接Avg_sc.Sno
就可以,在這個例題中因爲有聚集函數,所以也爲其指定了屬性列(名))
子查詢中沒有聚集函數的例子看下面這個:
🌟來看例子: 查詢所有選修了1號課程的學生姓名,可以用如下查詢完成:
SELECT Sname
FROM Student, ( SELECT Sno
FROM SC
WHERE Cno = '1')
AS SC1
WHERE Student.Sno = SC1.Sno;
SELECT Sno, Sname FROM Student; /*打印出Student表的所有學生的學號和姓名來參考*/
SELECT Sno
FROM SC
WHERE Cno = '1'; /*打印出子查詢產生的派生表SC1來參考*/
那 本文到這裏也就結束啦,感謝閱讀~😊,數據查詢部分 END,接下來該到數據更新部分啦,感謝大家一路以來的陪伴與支持💕
這裏是一個想把學習過程記錄成博客分享給大家的undergraduate,請多關照🙏
咱們下期 見~