SQL Server 數據庫基本操作入門篇【6】

本文主講數據查詢中的 嵌套查詢(EXISTS部分)、集合查詢、基於派生表的查詢,歡迎閱讀~


一、帶有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');

在這裏插入圖片描述
不同形式的查詢間的替換

  • 一些EXISTSNOT EXISTS謂詞的子查詢不能被其他形式的子查詢等價替換
  • 所有IN謂詞、比較運算符ANYALL謂詞的子查詢都能用帶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語言中沒有全稱量詞 \forall(For all)

可以把帶有全稱量詞的謂詞轉換爲等價的帶有存在量詞的謂詞:( \forall x )P \equiv ¬\neg\exists x(¬\neg P))
(ps:\equiv 表示恆等於)
這樣看不太好理解,咱通過例子來搞明白它!

🌟來看例子: 查詢選修全部課程的學生姓名:
(選修了全部課程可理解爲:沒有一門課程是他沒選修的)

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 \rightarrow q \equiv ¬\neg p∨q
同樣,通過例子來講解

🌟來看例子: 查詢至少選修了學生201215122選修的全部課程的學生號碼:
(這裏假如是李勇至少選修了他的全部課程,則可理解爲:李勇選修的課程裏面包含着他選修的所有課程)
· 思路:
用邏輯蘊涵表達:查詢學號爲x的學生,對所有的課程y,只要201215122學生選修了課程y,則x也選修了y(即x的課程裏含有201215122選修的所有課程
形式化表示:
用P表示謂詞 “學生201215122選修了課程y”
用q表示謂詞 “學生x選修了課程y”
則上述查詢爲: (\forally) p \rightarrow q (對任意的課程y,p都包含於q)
等價變換:(\forally)p \rightarrow q \equiv ¬\neg \existsy(p∧¬\negq)(因爲剛剛介紹了,SQL中沒有沒有全稱量詞 \forall,所以這裏仍然採用存在量詞來表示)
變換後的語義:不存在這樣的課程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 \cup
交:INTERSECT \cap
差: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,請多關照🙏
咱們下期 ~
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章