1 單關系查詢
1.1 投影查詢
SELECT [DISTINCT] 屬性名列表 FROM 關系名;
從一個關系中選出指定的列.
- 查詢結果中屬性的順序與屬性名列表給出的屬性順序相同;
- 不加
DISTINCT
, 則不去除結果中的重復元組; 加上DISTINCT
, 則去除結果中的重復元組; - 若要返回關系中所有的列, 可將屬性名列表簡寫為
*
.
例:
查詢學生的學號和姓名SELECT Sno, Sname FROM Student;
查詢所有系名
SELECT DISTINCT Sdept FROM Student;
查詢全部學生信息
SELECT * FROM Student;
可將投影查詢中的屬性名替換為表達式, 做更復雜的投影操作.
SELECT [DISTINCT] 表達式列表 FROM 關系名;
- 表達式可以是常量, 算數表達式, 函數表達式, 邏輯表達式;
- 查詢結果中表達式列的名稱就是該表達式的字符串, 可以用
表達式 AS 屬性名
將表達式列重命名.
例:
查詢學生的學號和姓名 (姓名全大寫)SELECT Sno, UPPER(Sname) FROM Student;
查詢學生的姓名和出生年份
SELECT Sname, (2021 - Sage) AS bd FROM Student;
1.2 選擇查詢
SELECT [DISTINCT] 表達式列表 FROM 關系名 WHERE 選擇條件;
從一個關系中選擇滿足給定條件的元組.
例:
查詢計算機系 (CS) 全體學生的學號和姓名SELECT Sno, Sname FROM Student WHERE Sdept = 'CS';
查詢計算機系 (CS) 全體男同學的學號和姓名
SELECT Sno, Sname FROM Student WHERE Sdept = 'CS' AND Ssex = 'M';
查詢計算機系 (CS) 和數學系 (Math) 全體學生的學號和姓名
SELECT Sno, Sname FROM Student WHERE Sdept = 'CS' OR Sdept = 'Math';
1.2.1 選擇查詢條件
選擇查詢條件 | 語法 | 功能 |
---|---|---|
表達式比較 | 表達式1 比較運算符 表達式2 |
比較運算符: = , < , <= , > , >= , != , <> |
范圍比較 | 表達式1 [NOT] BETWEEN 表達式2 AND 表達式3 |
判斷表達式1的值是否 (不) 在表達式2和表達式3之間 |
集合元素判斷 | 表達式1 [NOT] IN (表達式2, ..., 表達式n) |
判斷表達式1的值是否 (不) 在表達式2, ..., 表達式n的值構成的集合中 |
字符串匹配 | 字符串表達式 [NOT] LIKE 模式 [ESCAPE 轉義字符] |
判斷字符串表達式的值是否匹配給定的含有通配符的模式 通配符 _ : 匹配單個字符 通配符 % : 匹配任意長度的字符串 (含空串) 可通過 ESCAPE 子句指定轉義字符, 默認轉義字符\ |
字符串正則表達式匹配 | `字符串表達式 [NOT] REGEXP | RLIKE 模式`[1] |
空值 (NULL) 判斷 | 屬性名 IS [NOT] NULL |
判斷屬性值是否 (不) 為空 錯誤寫法: 屬性名 = NULL 屬性名 <> NULL 屬性名 != NULL 這些錯誤寫法結果均為UNKNOWN. |
邏輯運算 | 邏輯運算符: AND , OR , NOT |
只有使查詢條件為真的元組才能出現在查詢結果中 |
集合操作 | 求並: 查詢語句1 UNION [ALL] 查詢語句2 求交[2]: 查詢語句1 INTERSECT 查詢語句2 求差[3]: 查詢語句1 MINUS/EXCEPT 查詢語句2 |
求兩個查詢語句結果的並, 交, 差 查詢語句1的結果的屬性名將作為集合操作結果的屬性名; 若使用關建詞 ALL , 則並集不去重; 即使兩個查詢語句的結果都是有序的, 集合操作的結果也未必有序. |
例:
查詢姓名首字母為E的學生的學號和姓名 (字符串匹配)SELECT Sno, Sname FROM Student WHERE Sname LIKE 'E%';
查詢姓名為4個字母且首字母為E的學生的學號和姓名 (字符串匹配)
SELECT Sno, Sname FROM Student WHERE Sname LIKE 'E___';
查詢姓名首字母為E或F的學生的學號和姓名 (正則表達式)
SELECT Sno, Sname FROM Student WHERE Sname REGEXP '^[EF].*';
查詢選了課但還未取得成績的學生 (空值判斷)
SELECT Sno FROM SC WHERE Grade IS NULL;
查詢選修了1002號或3006號課的學生的選課信息 (集合操作)
SELECT * FROM SC WHERE Cno = '1002' UNION ALL SELECT * FROM SC WHERE Cno = '3006';
查詢選修了1002號或3006號課的學生的學號 (集合操作)
SELECT Sno FROM SC WHERE Cno = '1002' UNION SELECT Sno FROM SC WHERE Cno = '3006';
1.2.2 查詢結果排序
在查詢語句的后面加上OTDER BY 屬性名1 [ASC|DESC], ..., 屬性名n [ASC|DESC]
.
- 按照 (屬性1, ..., 屬性n)的字典序進行排序, 對查詢結果關系中的元組進行排序.
ASC
表示升序 (默認),DESC
表示降序.- 若排序屬性含空值,
ASC
: 屬性值為空的元組排在最前;DESC
: 屬性值為空的元組拍在最后. - 通常用
ORDER BY
子句對最終查詢結果排序, 而不對中間結果排序.
例:
查詢計算機系 (CS) 全體學生的學號和姓名, 並按學號升序排列SELECT Sno, Sname FROM Student WHERE Sdept = 'CS' ORDER BY Sno;
查詢全體學生的信息, 結果按所在系升序排列, 同一個系的學生按年齡降序排列
SELECT * FROM Student ORDER BY Sdept ASC, Sage DESC;
1.2.3 限制查詢結果數量
在查詢語句的后面加上LIMIT [偏移量,] 結果數量
或者LIMIT 結果數量 [OFFSET 偏移量]
[4].
- 限制查詢結果中元組的數量 (不是標准SQL的內容) 位置.
- 從偏移量 (默認是0) 位置的元組開始, 返回指定數量的元組.
例:
查詢3006號課得分最高的前2名學生的學號和成績SELECT Sno, Grade FROM SC WHERE Cno = '3006' ORDER BY Grade DESC LIMIT 2;
1.3 聚集(Aggregation)查詢
SELECT 聚集函數([DISTINCT] 表達式) FROM...WHERE...
計算一個關系上某表達式所有值的聚集值 (值的個數COUNT
, 最大值MAX
, 最小值MIN
, 總和SUM
, 平均值AVG
)[5].
語法 | 功能 |
---|---|
COUNT(*) |
所有元組的數量 |
COUNT(表達式) |
非空表達式值的數量 |
COUNT(DISTINCT 表達式) |
不同的非空表達式的數量 |
MAX([DISTINCT] 表達式) |
表達式的最大值 |
MIN([DISTINCT] 表達式) |
表達式的最小值 |
SUM(表達式) |
表達式值的和 |
SUM(DISTINCT 表達式) |
不同表達式值的和 |
AVG(表達式) |
表達式值的平均值 |
AVG(DISTINCT 表達式) |
不同表達式值的平均值 |
例:
查詢計算機系全體學生的數量SELECT COUNT(*) FROM Student WHERE Sdept = 'CS';
查詢計算機系學生的最大年齡
SELECT MAX(Sage) FROM Student WHERE Sdept = 'CS';
聚集函數不能出現在WHERE
子句中!
例:
查詢年齡最大的學生的學號SELECT Sno FROM Student WHERE Sage = MAX(Sage);
寫法錯誤, 正確的做法是使用嵌套查詢!
1.4 分組 (Group By) 查詢
SELECT 分組屬性列表, 聚集函數表達式列表 FROM 關系名 WHERE 選擇條件 GRUP BY 分組屬性列表;
- 根據指定的分組屬性, 對一個關系中的元組進行分組, 分組屬性值相同元組的分一組.
- 對每個組中元組的非分組屬性的值進行聚集.
- 分組查詢語句中不能包含分組屬性及聚集函數表達式以外的其他表達式.
例:
統計每門課的選課人數和平均成績SELECT Cno, COUNT(*), AVG(Grade) FROM SC GROUP BY Cno;
統計每個系的男生人數和女生人數
SELECT Sdept, Ssex, COUNT(*) FROM Student GROUP BY Sdept, Ssex;
分組完成后, 經常需要安裝組內元組的統計信息對分組進行篩選
SELECT 分組屬性列表, 聚集函數表達式列表 FROM 關系名 WHERE 選擇條件 GROUP BY 分組屬性列表 HAVING 分組篩選條件;
例:
查詢選修了2門以上課程的學生的學號和選課數SELECT Sno, COUNT(*) FROM SC GROUP BY Sno HAVING COUNT(*) >= 2;
查詢2門以上課程得分超過80的學生的學號及這些課程的平均分
SELECT Sno, AVG(Grade) FROM SC WHERE Grade > 80 GROUP BY Sno HAVING COUNT(*) >= 2;
注意事項:
SELECT
子句的目標列中只能包含分組屬性和聚集函數[6].WHERE
子句的查詢條件中不能出現聚集函數.HAVING
子句的分組篩選條件中可以使用聚集函數.WHERE
,GROUP BY
和HAVING
的執行順序- 按照
WHERE
子句給出的條件, 從關系中選出滿足條件的元組; - 按照
GROUP BY
子句指定的分組屬性, 對元組進行分組; - 按照
HAVING
子句給出的條件, 對分組進行篩選.
- 按照
2 連接查詢
2.1 內連接
SELECT ... FROM 關系名1 [INNER] JOIN 關系名2 ON 連接條件;
按照給定的連接條件, 對兩個關系做內連接.
例:
查詢學生及其選課情況, 列出學號, 姓名, 課號, 得分SELECT Student.Sno, Sname, Cno, Grade FROM Student JOIN SC ON (Student.Sno = SC.Sno);
當內連接是等值連接, 且連接屬性同名時, 可使用如下語法
關系名1 [INNER] JOIN 關系名2 USING (連接屬性列表)
例:
查詢學生及其選課情況, 列出學號, 姓名, 課號, 得分SELECT Student.Sno, Sname, Cno, Grade FROM Student JOIN SC USING (Sno);
2.2 自然連接
關系名1 NATURAL JOIN 關系名2
兩個關系做自然連接.
例:
查詢學生及其選課情況, 列出學號, 姓名, 課號, 得分SELECT Student.Sno, Sname, Cno, Grade FROM Student NATURAL JOIN SC;
2.3 自連接
一個關系與其自身進行連接.
- 參與連接的關系在物理上時同一個關系, 但在邏輯上看作兩個關系, 因此用
AS
必須重命名. - 當為關系取了別名后, 但凡用到該關系時都必須使用其別名, 不能再使用原關系名.
- 屬性名前必須加別名做前綴.
例:
查詢和Elsa在同一個系學習的學生的學號和姓名SELECT S2.Sno, S2.Sname FROM Student AS S1 JOIN Student AS S2 ON S1.Sdept = S2.Sdept AND S1.Sno != S2.Sno WHERE S1.Sname = 'Elsa';
2.4 外連接
兩個關系做外連接.
左外連接: 關系名1 LEFT [OUTER] JOIN 關系名2 ON 連接條件
右外連接: 關系名1 RIGHT [OUTER] JOIN 關系名2 ON 連接條件
全外連接: 關系名1 FULL [OUTER] JOIN 關系名2 ON 連接條件
[7]
全自然外連接: 關系名1 NATURAL LEFT|RIGHT [OUTER] JOIN 關系名2
當外連接時等值連接, 且連接屬性同名時, 可使用USING (連接屬性列表)
聲明連接條件.
例:
查詢沒有選課的學生的學號和姓名SELECT Student.Sno, Sname FROM Student LEFT JOIN SC ON (Student.Sno = SC.Sno) WHERE Cno IS NULL;
3 嵌套查詢
查詢塊: 一個SELECT-FROM-WHERE
語句稱為一個查詢塊 (block).
嵌套查詢: 將一個查詢塊嵌套在另一個查詢塊中得到的查詢稱為嵌套查詢 (nested query), 內層查詢塊稱為子查詢 (subquery).
子查詢的類型:
- 不相關子查詢 (independent subquery): 子查詢不依賴與外層查詢;
- 相關子查詢 (correlated subquery): 子查詢依賴於外層查詢.
例:
查詢和Elsa在同一個系學習的學生的學號和姓名 (含Elsa).
- 使用不相關子查詢
SELECT Sno, Sname FROM Student WHERE Sdept = (SELECT Sdept FROM Student WHERE Sname = 'Elsa');
- 使用相關子查詢
SELECT Sno, Sname FROM Student AS S WHERE EXISTS (SELECT * FROM Student AS T WHERE T.Sname = 'Elsa' AND T.Sdept = S.Sdept);
嵌套查詢的寫法:
- 在集合判斷條件中使用子查詢: 使用
[NOT] IN
- 在比較條件中使用子查詢: 使用
比較運算符
- 在存在性測試條件中使用子查詢: 使用
[NOT] EXISTS
- 子查詢結果作為派生關系
例:
查詢和Elsa在同一個系學習的學生的學號和姓名 (含Elsa)
在集合判斷條件中使用子查詢
SELECT Sno, Sname FROM Student WHERE Sdept IN (SELECT Sdept FROM Student WHERE Sname = 'Elsa');
在比較條件中使用子查詢
SELECT Sno, Sname FROM Student WHERE Sdept = (SELECT Sdept FROM Student WHERE Sname = 'Elsa');
在存在性測試條件中使用子查詢
SELECT Sno, Sname FROM Student AS S WHERE EXISTS (SELECT * FROM Student AS T WHERE T.Sname = 'Elsa' AND T.Sdept = S.Sdept);
3.1 在集合判斷條件中使用子查詢
表達式 [NOT] IN (子查詢)
判斷表達式的值是否 (不) 屬於子查詢的結果.
先執行子查詢, 后執行父查詢.
例:
查詢和Elsa在同一個系學習的學生的學號和姓名 (含Elsa)SELECT Sno, Sname FROM Student WHERE Sdept IN (SELECT Sdept FROM Student WHERE Sname = 'Elsa');
查詢選修了 "Database Systems" 的學生的學號和姓名
SELECT Sno, Sname FROM Student WHERE Sno IN (SELECT Sno FROM SC WHERE Cno IN (SELECT Cno FROM Course WHERE Cname = 'Database Systems'));
3.2 在比較條件中使用子查詢
表達式 比較運算符 [ALL|ANY|SOME] (子查詢)
將表達式的值與子查詢的結果進行比較. 先執行子查詢, 后執行父查詢.
ALL
: 當表達式的值與子查詢結果中任意的值都滿足比較條件時, 返回真; 否則返回假;ANY
或SOME
: 當表達式的值與子查詢結果中某個值滿足比較條件時, 返回真; 否則返回假;- 如果子查詢結果應僅包含單個值, 則無需在比較運算符后面加
ALL
,ANY
或SOME
.
例:
查詢和Elsa在同一個系學習的學生的學號和姓名 (含Elsa)SELECT Sno, Sname FROM Student WHERE Sdept = (SELECT Sdept FROM Student WHERE Sname = 'Elsa');
查詢年齡最大的學生的學號
SELECT Sno FROM Student WHERE Sage = (SELECT MAX(Sage) FROM Student);
查詢比計算機系 (CS) 全體學生年齡都大的學生的學號
SELECT Sno FROM Student WHERE Sage > ALL (SELECT Sage FROM Student WHERE Sdept = 'CS');
查詢學生平均年齡比全校學生平均年齡大的系
SELECT Sdept FROM Student GROUP BY Sdept HAVING AVG(Sage) > (SELECT AVG(Sage) FROM Student);
3.3 在存在性測試條件中使用子查詢
[NOT] EXISTS (子查詢)
判斷子查詢結果是否 (不) 為空.
子查詢的SELECT
后無需列出目標列, 只需用SELECT *
, 因為我們只判斷子查詢結果是否為空, 並不需要使用子查詢結果.
查詢和Elsa在同一個系學習的學生的學號和姓名 (含Elsa)
SELECT Sno, Sname FROM Student AS S WHERE EXISTS (SELECT * FROM Student AS T WHERE T.Sname = 'Elsa' AND T.Sdept = S.Sdept);
查詢執行過程:
- 從\(Student\)關系中依次取出每條元組\(t\), 設\(t\)的\(Sdept\)屬性值為\(t[Sdept]\);
- 將\(t[Sdept]\)代入子查詢后, 執行子查詢;
- 若子查詢的結果不為空, 則將\(t\)投影到\(Sno\)和\(Sname\)屬性上, 並輸出投影后的元組.
用EXISTS
實現全稱量詞\(\forall\)功能:
SQL不支持全稱量詞\(\forall\) (for all), 用EXISTS
實現全稱量詞, 因為 \(\forall x(P(x)) = \nexists x(\neg{P(x)})\)
例:
查詢選修了全部課程的學生的學號SELECT Sno FROM Student WHERE NOT EXISTS ( SELECT * FROM Course WHERE NOT EXISTS ( SELECT * FROM SC WHERE SC.Sno = Student.Sno AND SC.Cno = Course.Cno));
思路: 設 \(t \in Student\)是某個選修了全部課程的學生的元組,
則 \(\forall c \in Course\), 必 \(\exists s \in SC\), 使 \(s[Sno] = t[Sno] \wedge s[Cno] = c[Cno]\)
這等價於:
\(\nexists c \in Course\), 使得 \(\nexists s \in SC\), 使 \(s[Sno] = t[Sno] \wedge s[Cno] = c[Cno]\).
用EXISTS
實現邏輯蘊含\(\rightarrow\)功能:
SQL不支持邏輯蘊含\(rightarrow\) (implication), 可以用EXISTS
實現邏輯蘊含, 因為 \(x \rightarrow y = \neg{x} \vee y\).
例:
查詢至少選修了CS-001號學生選修的全部課程的學生的學號SELECT Sno FROM Student WHERE NOT EXISTS ( SELECT * FROM SC AS SC1 WHERE SC1.Sno = 'CS-001' AND NOT EXISTS ( SELECT * FROM SC AS SC2 WHERE SC2.Sno = Student.Sno AND SC2.Cno = SC1.Cno));
3.4 子查詢結果作為派生關系
FROM(子查詢)
- 派生表 (derived table): 將子查詢的結果當作關系放在外層查詢的
FROM
子句中使用, 稱為派生表; - 子查詢必須是獨立子查詢;
- 派生表必須重命名.
例:
查詢選修了2門以上課程的學生的學號和選課數SELECT Sno, T.Amt FROM (SELECT Sno, COUNT(*) AS Amt FROM SC GROUP BY Sno) AS T WHERE T.amt >= 2;
MySQL使用關建詞
REGEXP
或RLIKE
;
Oracle使用關建詞REGEXP _LIKE
;
MS SQL Server使用關建詞LIKE
. ↩︎Oracle和MS SQL Server支持
INTERSECT
, MySQL不支持INTERSECT
. ↩︎Oracle用
MINUS
, MS SQL Server用EXCEPT
, MySQL兩者都不支持. ↩︎Oracle和MS SQL Server使用不同的語法. ↩︎
MySQL還支持其他聚集函數, 如
GROUP_CONCAT
(拼接),VARIANCE
(方差),STD
(標准差) https://dev.mysql.com/doc/refman/5.5/en/group-by-functions.html. ↩︎單純出於性能考慮, MySQL允許某些非分組屬性出現在目標列中 https://dev.mysql.com/doc/refman/5.5/en/group-by-handling.html. ↩︎
Oracle和MS SQL Server支持
FULL OUTER JOIN
, MySQL不支持FULL OUTER JOIN
. ↩︎