主要內容
數據查詢
數據查詢是數據庫的核心操作,該篇將着重介紹數據查詢的五種方式:單表查詢、連接查詢、嵌套查詢、集合查詢和基於派生表的查詢。
基本語法
一般格式(尖括號“<>”表示必填,“[]”表示可選,“|”用於區別不同選擇):
SELECT [ ALL | DISTINCT ] <目標列表達式> [,<其餘目標列表達式>]
FROM <表名或視圖名> [,<其餘表名或視圖名>]
[ WHERE <條件表達式> ]
[ GROUP BY <列名> [ HAVING <條件表達式> ] ]
[ ORDER BY <列名> [ ASC | DESC ] ] ;
(1)整個SELECT語句的含義是,根據WHERE子句的條件表達式,從FROM子句指定的範圍(基本表、視圖或派生表)中找出滿足條件的元組,再按SELECT子句中的目標列表達式選出元組中的屬性值形成結果表。
(2)形成的結果表可能會含有重複行,若SELECT子句中指定ALL關鍵詞,則保留結果表中的所有重複行。反之,若指定DISTINCT關鍵詞,則去掉重複行。默認爲ALL。
(3)GROUP BY子句用於對查詢結果(執行SELECT-FROM語句後的結果)按屬性值(同一列或幾列相應的值)相等的規則進行分組,隨後可用HAVING子句對劃分好的組依組處理。
(4)ORDER BY子句用於對查詢結果根據某一列或幾列的屬性值,按升序(ASC)或降序(DESC)排列。
可能看完上面這一坨拗口(雖然我已經很努力在概括了)的句子後,大家還是雲裏霧裏的。不用擔心,後面會繼續作介紹。
單表查詢
單表查詢是指僅涉及一個表的查詢。
1. 選擇表中的若干列
SELECT-FROM語句對應關係代數的“投影”(我是鏈接)
(1)指定查詢
如查詢全體學生的學號與名字:
SELECT Sno,Sname
FROM Student;
(2)全部查詢
如查詢全體學生的詳細信息(學號,姓名,性別,年齡,院系):
SELECT Sno,Sname,Ssex,Sage,Sdept
FROM Student;
SQL提供了用於全選語法糖,如果你允許結果表中屬性列的排列順序與原表相同,則上面的語句等價於
SELECT *
FROM Student;
(3)表達式查詢
如查詢學生姓名、出生年份(假設今年是2018年)和所在院系,並且所在院系用小寫字母表示:
SELECT Sname, 'Year of Birth:', 2018-Sage BIRTHDAY, LOWER(Sdept) DEPARTMENT
FROM Student;
分析:
'Year of Birth:' —— 字符串常量直接輸出
2018-Sage BIRTHDAY —— 算術表達式求出生年份,並且該列屬性名取別名爲“BIRTHDAY”
LOWER(Sdept) DEPARTMENT —— 使用SQL函數LOWER()實現字母轉換成小寫的功能,並取別名爲“DEPARTMENT”
2. 選擇表中的若干元組
(1)對重複行的處理判斷
SQL語句先處理列,再處理行。兩個本來不完全相同的元組,在投影到某些列後,可能會出現重複行,若指定DISTINCT關鍵詞,重複行會被消除,若指定ALL關鍵詞,則重複行被保留,系統默認爲ALL。
(2)查詢滿足條件的元組
SELECT-FROM-WHERE語句對應關係代數的“選擇”。
查詢條件 | 謂詞 |
比較 | [NOT]比較運算符 |
確定範圍(包括端點) | [NOT] BETWEEN AND |
確定集合 | [NOT] IN |
字符匹配 | [NOT] LIKE |
空值 | IS NULL,IS NOT NULL |
多重條件 | AND,OR,NOT |
//查詢年齡在20~23歲之間(包括20和23歲)的學生的姓名:
SELECT Sname
FROM Student
WHERE Sage BETWEEN 20 AND 23;
//查詢計算機科學(CS)、數學系(MA)的學生姓名:
SELECT Sname
FROM Student
WHERE Sdept IN ('CS','MA');
//查詢無成績的學生的姓名和學號:
SELECT Sname,Sno
FROM Student
WHERE Grade IS NULL;
(3)字符匹配
在字符匹配中,“LIKE”等價於“=”。
此外,在字符匹配中涉及到兩個通配符(%和_)和換碼字符短語(ESCAPE '\')
%代表任意長度的字符串,例如a%b,表示以a爲開頭,b爲結尾的字符串;
_代表任意單個字符;
ESCAPE '\'表示“\”後緊跟的字符“%”或“_”不作爲通配符使用。
//查詢所有姓劉的學生的姓名和學號:
SELECT Sname,Sno
FROM Student
WHERE Sname LIKE '劉%';
//查詢姓“歐陽”且名字只有三個字的學生的姓名和學號:
SELECT Sname,Sno
FROM Student
WHERE Sname LIKE '歐陽_'
//查詢DB_Design課程的課程號和學分:
SELECT Cno,Ccredit
FROM Student
WHERE Cname LIKE 'DB\_Design' ESCAPE '\';
3. ORDER BY子句
//查詢選修了3號課程的學生的學號及其成績,查詢結果按分數的降序排序:
SELECT Sno,Grade
FROM SC
WHERE Cno = '3'
ORDER BY Grade DESC;
需要留意的是,對於空值,排序時顯示的順序由具體系統來決定。
4. 聚集函數
COUNT(*) | 統計元組個數 |
COUNT( [ DISTINCT | ALL ] ) | 統計一列中值的個數 |
SUM( [ DISTINCT | ALL ] ) | 計算一列值的總和 |
AVG( [ DISTINCT | ALL ] ) | 計算一列值的平均值 |
MAX( [ DISTINCT | ALL ] ) | 求一列值中的最大值 |
MIN( [ DISTINCT | ALL ] ) | 求一列值中的最小值 |
注意點:
(1)指定DISTINCT關鍵詞,表示計算時要忽略重複值。如果不指定DISTINCT,則默認爲ALL關鍵詞,將重複值計算在內。
(2)WHERE子句不能用聚集函數作爲條件表達式,聚集函數只能在SELECT子句和HAVING子句中使用。WHERE子句與HAVING子句的區別在於作用對象不同。WHERE子句作用於基本表或視圖,從中選擇滿足條件的元組,而HAVING子句作用於分組,從中選擇滿足條件的組。
(3)除COUNT(*)外,其他聚集函數遇到空值後都會自動跳過。
//查詢學生總人數:
SELECT COUNT(*)
FROM Student;
//查詢選修了課程的學生人數:
SELECT COUNT(DISTINCT Sno)
FROM SC;
//計算選修1號課程的學生平均分:
SELECT AVG(Grade)
FROM SC
WHERE Cno = '1';
5. GROUP BY子句
(1)GROUP BY子句將查詢結果按一列或多列的屬性值分組,組相等的行歸爲一組。若不對查詢結果進行分組,聚集函數將作用於整個查詢結果。分組後,聚集函數會作用於每個小組並返回一個函數值。
//求各課程號及相應的選課人數:
SELECT Cno,COUNT(Sno) /*分組後,COUNT函數會對每個組都進行計算*/
FROM SC
GROUP BY Cno; /*對查詢結果按Cno的值分組*/
(2)GROUP BY子句還可以配合上HAVING短語(回顧上面的注意點2)指定篩選條件。
//查詢選修了三門以上課程的學生學號和選修的課程數量:
SELECT Sno
FROM SC
GROUP BY Sno /*對查詢結果按Sno的值分組*/
HAVING COUNT(*) > 3; /*分組後 * 會依次表示不同的組,COUNT函數再計算每個組的元組數*/
連接查詢
連接查詢是指同時涉及兩個以上的表的查詢。
1. 等值與非等值連接查詢
連接查詢中的WHERE子句是由連接條件和選擇條件組成的複合條件。執行復合條件的連接查詢時是先執行選擇條件,再執行連接條件,這是一種高效執行過程。
連接條件是指用比較運算符連接兩個表中的某個屬性列,連接條件中的列名稱爲連接字段,字段的類型必須是可比的,但名字不必相同。如果屬性列名稱不同,可以省略表名前綴。
當連接條件中的比較運算符爲“=”時,連接稱爲等值連接,否則稱爲非等值連接。
有時等值連接後會出現重複列,將重複列取消的連接稱爲自然連接。(可回顧“專門的關係運算”)
//查詢每個學生及其選修課程的情況:
<等值連接>
SELECT Student.*,SC.* /*Student.*和SC.*表示兩表中的所有屬性列,所以表中會出現重複列*/
FROM Student,SC
WHERE Student.Sno = SC.Sno;
<自然連接>
SELECT Student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade /*雖然表中不會出現重複列,但是代碼變長了_(:з」∠)_*/
FROM Student,SC
WHERE Student.Sno = SC.Sno;
//查詢選修2號課程,且成績在90分以上的所有學生姓名和學號:
<與選擇條件複合>
SELECT Student.Sno,Sname
FROM Student,SC
WHERE Student.Sno = SC.Sno /*連接條件*/
AND Cno = '2' /*選擇條件*/
AND Grade >= 90;
2. 自身連接
連接操作不僅可以在不同表上進行,還可以是表與自身的連接。
爲了實現自身連接,同時保證邏輯的正確性,我們需要給表起若干個別名。
//查詢每一門選修課(Cno)的先修課程(Cpno):
<思路>選修課和先修課同位於表Course,我們需要起兩個別名,分別表示選修課和先修課所在的表。
SELECT FIRST.Cno,SECOND.Cpno /*FIRST表是選修課所在表,SECOND表是先修課所在表*/
FROM Course FIRST,Course SECOND /*給Course起兩個別名*/
WHERE FIRST.Cpno = SECOND.Cno; /*在FIRST表中找先修課,再在SECOND表中找課程號*/
3. 外連接
採用外連接可以使被捨棄的懸浮元組顯示在查詢結果中。(可回顧“專門的關係運算”)
//查詢每個學生及其選修課的情況
SELECT Student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade
FROM Student LEFT OUTER JOIN SC USING(Sno); /*USING(Sno)等價於Student.Sno = SC.Sno*/
細心的同學可以注意到,外連接裏我們沒有用到WHERE子句來連接兩個表,這是因爲例子中使用了JOIN短語。在FROM子句中使用JOIN就可以實現連接,而且
FROM Student INNER JOIN SC ON Student.Sno = SC.Sno; 等價於 WHERE Student.Sno = SC.Sno;
NATURAL JOIN 表示自然連接
LEFT OUTER JOIN 表示左外連接(同理可推導右外連接)
連接查詢時使用WHERE還是JOIN就根據大家的編程習慣而定嘍。
4. 多表連接
//查詢每個學生的姓名、選修課程及成績:
SELECT Sname,Cno,Grade
FROM Student,Course,SC
WHERE Student.Sno = SC.Sno AND Course.Cno = SC.Cno;
嵌套查詢
在SQL語言中,我們司空見慣的SELECT-FROM-WHERE語句被稱爲一個查詢塊。將一個查詢塊嵌套在另一個查詢塊的WHERE子句或HAVING短語中的查詢,我們定義爲嵌套查詢。
嵌套查詢使用戶可以用多個簡單的查詢構成複雜的查詢,增強SQL的查詢能力,這種查詢正是SQL(結構化查詢語言)中“結構化”的含義所在。
1.帶有IN謂詞的子查詢
在嵌套查詢中,子查詢的結果往往是一個集合,IN正是我們之前提到的“確定集合”的查詢謂詞。
//查詢選修了課程名爲“信息系統”的學生學號和名字:
<嵌套查詢>
SELECT Sno,Sname
FROM Student
WHERE Sno IN /*得到學生學號*/
(SELECT Sno
FROM SC /*用SC表將Student表和Course表聯繫起來
WHERE Cno IN /*得到課程號*/
(SELECT Cno
FROM Course
WHERE Cname = '信息系統'
)
);
<連接查詢>
*有些嵌套查詢可以用連接替代,有些卻不可以。但在實際應用中,能夠用連接查詢的儘量用連接查詢!
SELECT Student.Sno,Sname
FROM Student,Course,SC
WHERE Student.Sno = SC.Sno
AND SC.Cno = Course.Cno
AND Course.Cname = '信息系統';
*在上面的嵌套查詢中,子查詢的查詢條件不依賴於父查詢,子查詢的查詢結果用於建立父查詢的查詢條件,這樣的查詢叫做不相關子查詢。下面要介紹的嵌套查詢都屬於相關子查詢,即子查詢的查詢條件依賴於父查詢。求解相關子查詢不能像求解不相關子查詢那樣一次將子查詢解出來,然後求解父查詢。由於子查詢的查詢條件與父查詢相關,所以需要反覆求值。
2. 帶有比較運算符的子查詢
這種查詢用比較運算符來連接父查詢和子查詢,即用比較運算符取代IN短語。
//找出每個學生超過自己選修課平均成績的課程號:
SELECT Sno,Cno
FROM SC x /*給SC起兩個別名*/
WHERE Grade >=
(SELECT AVG(Grade)
FROM SC y
WHERE x.Sno = y.Sno
);
3. 帶有ANY(SOME)或ALL謂詞的子查詢
子查詢返回單值時可以用比較運算符,但返回多值時要加上ANY(有的系統用SOME)或ALL謂詞修飾符。
短語 | 語義 |
>ANY |
大於最小值 |
<ANY | 小於最大值 |
>ALL | 大於最大值 |
<ALL | 小於最小值 |
//查詢非計算機科學系中比計算機科學系任意一個學生年齡小的學生姓名和年齡:
/*要注意,這裏的“任意一個”並不是我們日常理解的“所有”。前面說過相關子查詢的嵌套查詢時反覆求值的,因此這裏的“任意一個”是指每一次求值時的“所有”,題幹要表達“所有”的時候一般會說“全部”。但其實用詞也沒那麼死板.......具體問題具體分析吧。*/
<使用ANY謂詞>
SELECT Sname,Sage
FROM Student
WHERE Sage <ANY
(SELECT Sage
FROM Student
WHERE Sdept = 'CS')
AND Sdept != 'CS'; /*不等於也可以用<>,其實從敲鍵盤的角度來說,<>還舒服一點*/
/*而且要注意,最後一行AND語句是父查詢的選擇條件之一*/
<使用聚集函數>
*在實際應用中,用聚集函數的查詢效率更高!
SELECT Sname,Sage
FROM Student
WHERE Sage <
(SELECT MAX(Sage)
FROM Student
WHERE Sdept = 'CS')
AND Sdept != 'CS';
4. 帶有EXISTS謂詞的子查詢
EXISTS謂詞只關心子查詢有無返回值,有爲真,無爲假,而不需要查詢具體值,所以有時候EXISTS是高效的查詢方法。
若WHERE子句返回真,則在父查詢中將正在被查詢的元組的對應屬性值放入結果表,然後再查詢下一個元組。若返回假,則跳過。
(1)存在量詞EXISTS
//查詢所有選修了1號課程的學生姓名:
SELECT Sname
FROM Student
WHERE EXISTS
(SELECT * /*由EXISTS引出的子查詢,目標列表達式通常用 * ,因爲EXISTS只關心有無返回值,給列名無實際意義*/
FROM SC
WHERE Sno = Student.Sno AND Cno = '1'
);
//查詢沒有選修1號課程的學生姓名
<NOT EXISTS>
...
WHERE NOT EXISTS
... /*其餘部分不用變*/
*遇到否定句式的問題時,先轉換爲肯定句式來寫出代碼,再添加NOT,這樣就清晰很多了。
(2)全稱量詞NOT EXISTS-NOT EXISTS
SQL不提供全稱量詞,但我們可以用離散數學的知識,用EXISTS表示出來,即NOT EXISTS-NOT EXISTS(沒有不...的)。
//查詢選修了全部課程的學生姓名:
<思路> 沒有一門是他不選修的 => 存在課程是他有選修的
第一步:從Student表中選擇一名學生開始查詢:
SELECT Sname
FROM Student
WHERE
第二步:存在一門課程:
EXISTS
(SELECT *
FROM Course
WHERE
第三步:存在正在查詢的學生選修的課程:
EXISTS
(SELECT *
FROM SC
WHERE Sno = Student.Sno /*在SC表中對比正在查詢的學生*/
AND Cno = Course.Cno
)
第四步:作思路的逆轉換,存在課程是他有選修的 => 沒有一門是他不選修的,即在EXISTS前加上NOT,並將前面的三部分連接起來:
SELECT Sname
FROM Student
WHERE NOT EXISTS
(SELECT *
FROM Course
WHERE NOT EXISTS
(SELECT *
FROM SC
WHERE Sno = Student.Sno
AND Cno = Course.Cno
)
); /*恭喜,答案出來了!*/
(3)蘊含(→)
SQL亦不提供蘊含的邏輯運算,但我們可以運用離散數學的知識表示出來,p→q ≡ 非p∨q ,即NOT EXISTS-OR。(但是在實際問題中,往往還需要進行若干次轉化)
//查詢至少選修了學生20170330選修的全部課程的學生學號:
<思路> 問題轉換。學生20170330選修了課程y,記爲事件;其餘學生x選修了課程y,記爲事件q。所以問題轉換爲,對於所有課程y,p都能推出(蘊含)q 。更進一步推導,只用存在或不存在來描述,不存在課程y,學生20170330選修了,而且不存在於學生x的選修課中。
從上面的推論可以得出,我們需要三個SC表(使用別名),一個用來選擇學生x,一個用來確定學生20170330選擇的課程,一個用來建立兩者新的聯繫。
第一步:從SC表中選擇一名學生開始查詢,因爲學生和他相應的課程信息都在該表中:
SELECT Sname
FROM SC X
WHERE
第二步:存在學生20170330選修了的課程y:
EXISTS
(SELECT *
FROM SC Y
WHERE Sno = '20170330'
第三步:而且存在學生x沒有選修課程y:
AND EXISTS
(SELECT *
FROM SC Z
WHERE Z.Sno = X.Sno
AND Z.Cno = Y.Cno
)
第四步:反轉,在EXISTS前加上NOT連接上面的三個部分:
SELECT Sname
FROM SC X
WHERE NOT EXISTS
(SELECT *
FROM SC Y
WHERE Y.Sno = '20170330'
AND NOT EXISTS
(SELECT *
FROM SC Z
WHERE Z.Sno = X.Sno
AND Z.Cno = Y.Cno
)
); /*恭喜,答案出來了!*/
集合查詢
每一個查詢塊的查詢結果都是元組的集合,而對於集合,我們可以進行並(UNION)、交(INTERSECT)和差(EXCEPT)操作。(可回顧“傳統的集合運算”)
需要注意的是,參加集合操作的查詢結果必須列數相同,而且對應項的數據類型也必須相同。
使用形式:
(查詢塊1)
[ UNION | INTERSECT | EXCEPT]
(查詢塊2)
基於派生表的查詢
子查詢不僅可以如嵌套查詢那樣,出現在WHERE子句中,它還可以出現在FROM子句中,這時子查詢又被稱爲臨時派生表。
//找出每個學生超過自己的選修課平均成績的課程號:
SELECT
FROM SC,( SELECT Sno,Avg(Grade)
FROM SC
GROUP BY Sno)
AS Avg_sc(Avg_sno,Avg_grade) /*AS短語用於給臨時表命名,並指明屬性列*/
WHERE SC.Sno = Avg_sc.Avg_sno AND SC.Grade >= Avg_sc.Avg_grade;
/*如果子查詢沒有使用如AVG()這樣的聚集函數,派生表可以不指明屬性列,因爲屬性列沒有被改變。*/
路過的圈毛君:“這篇《數據查詢》是我忙裏偷閒花了三天才爆肝出來的,有木有感覺超級良心呀~希望能給大家對數據查詢的理解帶來幫助_(:з」∠)_”