sql join 子查詢_跟飛哥學程式設計:SQL入門-10:子查詢
技術標籤:sql join 子查詢sql 子查詢sql 找到最近的值sql子查詢sql遞迴查詢所有子節點成績的查詢和排序功能實現
![be42ae9e3e7dd8de1ff73a03b617741f.png](https://img.796t.com/res/2020/12-19/05/145efa65d3dc68aefe3d53477de640cf.png)
我們來看這麼一個需求:
查詢每個學生的成績和所有同學平均成績的差距。
直觀的想,實現這個需求,至少需要兩個SELECT:
- 一個SELECT獲取所有同學的平均成績
- 另一個SELECT獲取每個同學和平均成績之間的差距及其他資訊
所以,我們就需要使用一個叫做 子查詢 的技術,其SQL程式碼如下所示:
SELECT Id, Reward, Reward-(SELECT AVG(Reward) FROM TProblem) AS Gap FROM TProblem
檢視執行計劃,可以發現確實進行了兩次SELECT操作:
![3985e0fab0da34ea5545c30b92175a62.png](https://img.796t.com/res/2020/12-19/05/de6c49cf4279edaf0f53fd439ef5d3cd.png)
子查詢除了可以使用在SELECT子句中,也可以使用在其他地方。比如我們來看另外一個需求:
用id=7的學生的成績替換掉id=8的學生成績
我們當然要使用UPDATE,然後使用子查詢獲得UPDATE應使用的值,其SQL語句如下所示:
UPDATE Student
SET Score = (SELECT Score FROM Student WHERE Id = 7)
WHERE Id = 8
我們可以看出,這種子查詢就是將SELECT的查詢結果當做一個列來使用,在任何可以使用“列名”的地方,都可以使用子查詢。
子查詢的結果可是一個標量,也可以是一個集合。
比如,我們看這麼一個需求:
刪除Student表中成績的重複記錄
如何來判讀重複?很難!所以我們就得“逆向思維”,能不能找到“唯一”的那些記錄特徵,然後將其和整表進行對比?所以就是DELETE和SELECT的組合操作:
DELETE Student
WHERE Id NOT IN ( -- 使用NOT IN進行集合操作
SELECT MAX(Id) FROM Student GROUP BY Score) -- 其結果是一個集合
注意:通過Score進行分組,但不要以Score為結果值進行比較。使用Max(Id)獲取Id,Id才能唯一標識一行資料。
上述子查詢都被歸類為獨立子查詢,其特點是子查詢是獨立的,不依賴於子查詢SQL語句以外的變數;換言之,你完全可以獨立的執行子查詢。
還有點懵?沒關係,我們接著看例子:
繼續使用之前的考試成績(Exam)表,
查詢並統計出每個學生分數最高的考試成績資訊(不僅僅是最高分數)
如果僅僅是每個學生的最高分數,用MAX(Score)...GROUP BY [UserName]就OK了,但是我們還要獲取其他資訊,怎麼辦呢?用我們剛剛學到的子查詢,這樣寫,有沒有問題?
SELECT * FROM Exam
WHERE Score IN(SELECT MAX(Score) FROM Exam GROUP BY [UserName])
結果是不行的。比如王新,他的最高成績應該是91,但結果裡還包括了他89的一行:
![f25db753ad6f0791bb95f4488d77930e.png](https://img.796t.com/res/2020/12-19/05/641c3ea2a3b0955cd3e44b418a212121.png)
為什麼呢?因為子查詢中Max(Score)了獲取了所有人的最好成績。89是陳元的最好成績,卻不是王新的最好成績啊!上述SQL語句,就類似於:
SELECT * FROM Exam WHERE Score IN(89,93,95,...)
結果就是隻要成績是89的所有行都滿足條件了。
所以得在子查詢中“定位到人”,正確的寫法:
SELECT * FROM Exam oe -- 給外部表一個別名oe(必須)
WHERE Score = (
SELECT MAX(Score) FROM Exam ie -- 給內部表一個別名ie(必須)
WHERE oe.UserName = ie.UserName -- 在內部查詢中使用了外部的UserName
-- 不再需要GROUP
)
注意其語法要點:
- 內表外表都必須加別名,以便於區分
- 然後使用表別名引導列名
這樣查詢的結果就對了:
![b0b0ff4fdee60bd45c95e1dee708c579.png](https://img.796t.com/res/2020/12-19/05/ae2548baef639c553e344099d236c4b2.png)
從邏輯上我們可以這樣理解:
- 在外表從上到下依次遍歷,
- 每選中一行,就把這一行的UserName傳入子查詢中
- 子查詢使用上一步獲得的外表UserName進行MAX()運算,獲得外表指定的UserName的最好成績,並傳遞給外表
- 外部利用子查詢獲得的(最好)成績和本行Score進行比較:結果為真,保留;否則過濾
但是,這只是我們“邏輯上地”“想象中地”,而不是實際上SQL Server的執行過程。檢視執行計劃:
![b4f989f0f22ef5d5cbe714df5db89587.png](https://img.796t.com/res/2020/12-19/05/d93b48e0fbcf56c1c8710cc48b47ce6a.png)
事實上SQL Server是利用了Sort(排序)、Segment(分組)和TOP(擷取)就完成了上述需求。其中的原理,鑑於課程安排,我們就不做介紹了。但同學們通過這個例子,應該可以深刻的體會:
SQL語言是一種指令性性的、目標導向的語言
了吧?
這種在子查詢內部中使用外部變數的查詢,就被稱之為相關子查詢。(請對比獨立子查詢理解)
為了鞏固學習內容,我們再來實現一個需求:
查出每個學生的其成績高於等於該學生平均成績的Exam資訊
SELECT * FROM Exam oe
WHERE Score >= (SELECT AVG(Score) FROM Exam ie WHERE oe.Author = ie.Author)
作業:
使用子查詢:
- 找到從未成為邀請人的使用者(當心NULL值)
- 查出這些文章:其作者總共只發布過這一篇文章
- 為求助新增一個釋出時間(PublishTime),查詢每個作者最近釋出的一篇求助
- 查出每個作者懸賞最多的3篇求助
- 刪除懸賞相同的求助(只要相同的全部刪除一個不留)
- 刪除每個作者懸賞最低的求助
----------------------------
暫時不做:如果一篇求助的關鍵字大於3個,將它的懸賞值翻倍
感謝童鞋們的閱讀!^_^
我就是:黑律師/包工頭/創業狗/老碼農……現在還是教書匠的大飛哥。
再次重申這個系列的目標是:
1)通俗易懂。2)實戰為主。3)面向就業。
系列內容的完善需要你的反饋!
歡迎點贊和評論,以及加入我們的QQ交流群:326801052。