子查詢
子查詢的表示形式為:(SELECT 語句),它是IN
、EXISTS
等運算符的運算數,它也出現于FROM子句和VALUES子句。包含子查詢的查詢叫做嵌套查詢。嵌套查詢分為相關嵌套查詢和不想關嵌套查詢
WHERE子句中的子查詢
比較運算符
子查詢的結果是元組的集合,即一個表,一般情況下,其關系模式有多列,關系實例有若干行。如果子查詢的結果是一個單列且單行的表,則可作為比較運算符的運算對象
例1 查詢與學號2000012的學生在同一個系的學生的詳細信息
注:本文所有例題涉及的關系實例如下:
學生信息都在Student表中;要查詢全部信息,使用*選擇所有列;查詢條件是和學號為‘2000012’的學生同一個系。
可以先查詢學號為‘2000012’的學生的系,再作為父查詢的條件查詢最終結果
SELECT * --父查詢
FROM Student
WHERE Sdept = (SELECT Sdept --子查詢FROM StudentWHERE Sno = '2000012');
查詢結果
(實驗環境:SQL Sever 2012)
上面的SQL語句的WHERE子句中出現了子查詢,該語句的執行過程為:先執行子查詢,得到一個值’計算機’,再用這個值替換子查詢,得到一個新的SQL語句:
SELECT * --父查詢
FROM Student
WHERE Sdept = '計算機';
然后執行新的SQL語句,得到結果。
該例的子查詢不依賴于父查詢而單獨執行,這樣的嵌套查詢叫做不相關嵌套查詢
例:查詢選修了1156號課程并且成績大于該課程平均成績的學生的學號和成績
SELECT Sno, Grade
FROM SC
WHERE Cno = '1156' AND Grade > (SELECT AVG(Grade)FROM SCWHERE Cno = '1156');
因為AVG是一個聚集函數,所以子查詢返回一個單行單列的數據,可以按上述方法使用
如果子查詢返回一個單列多行的表,則這個子查詢不能直接出現在比較表達式,需要使用SOME
或ALL
修飾符,SOME是值集合的某一元素,ALL代表集合的全體元素
例:查詢其他系比管理系某一學生年齡小的學生的姓名和年齡
查詢管理系學生的年齡:
SELECT Sage
FROM Student
WHERE Sdept = '管理';
該查詢結果為{18, 19, 19},不是一個單值。比某一學生年齡小要使用表達式Sage<SOME(18,19,19),如果Sage的值小于集合中某一元素的值,則比較結果為真,否則為假
SELECT Sname, Sage
FROM Student
WHERE Sdept <> '管理' AND Sage < SOME (SELECT SageFROM StudentWHERE Sdept = '管理');
上面的查詢還可以使用聚集函數實現
比某一學生年齡就,就是要小于所有學生年齡的最大值
SELECT Sname, Sage
FROM Student
WHERE Sdept <>'管理' AND Sage < (SELECT MAX(Sage)FROM StudentWHERE Sdept = '管理');
例:查詢其他系比管理系所有學生年齡都小的學生的姓名和年齡
SELECT Sname, Sage
FROM Student
WHERE Sdept <> '管理' AND Sage < ALL(SELECT SageFROM StudentWHERE Sdept = '管理');
該查詢同樣可以使用聚集函數
比所有學生年齡小,就是要小于所有學生年齡的最小值
SELECT Sname, Sage
FROM Student
WHERE Sdept <>'管理' AND Sage < (SELECT MIN(Sage)FROM StudentWHERE Sdept = '管理');
不存在這樣的學生,所以查詢結果為空
SOME、ALL修飾符與聚集函數及謂詞IN的對應關系如表所示
= | <> | < | <= | > | >= | |
---|---|---|---|---|---|---|
SOME | IN | <MAX | <=MAX | >MIN | >=MIN | |
ALL | NOT IN | <MIN | <=MIN | >MAX | >=MAX |
例:查詢平均成績不小于85分的學生的姓名和所在系
按照題意,設計查詢過程:首先從Student表中任取一個學生,假設為x,然后從SC表中匯總出x的平均成績,如果平均成績不小于85,則輸出x的姓名和所在系
求學生x的平均成績的SQL語句為:
SELECT Sno, AVG(Grade)
FROM SC
WHERE Sno = x.Sno;
在SELECT語句中,除了表名、列名外,現在還出現了一個變量x,x叫做元組變量,表示某個表的一個元組。元組變量的名稱只能是表名或表的別名,這樣,通過元組變量的名稱就知道它代表哪個表的元組
最終查詢語句如下:
SELECT x.Sname, x.Sdept
FROM Student x
WHERE (SELECT AVG(Grade)FROM SCWHERE x.Sno = SC.Sno) > 85;
這樣的嵌套查詢叫做相關嵌套查詢,因為子查詢有一個變量x,當未確定x的值時,無法得到查詢結果,而x代表父查詢的元組,與父查詢相關。不相關嵌套查詢的子查詢先于父查詢執行,并且只執行一次,而相關嵌套查詢對父查詢的每個元組都要執行一次子查詢
上述語句的執行過程如下:
-
執行父查詢,順序掃描Student表
-
取Student表的一個元組賦予元組變量x
-
執行父查詢的WHERE子句
- 將第2步獲取的x傳送到子查詢
- 執行子查詢,得到平均成績
- 判斷平均成績是否大于等于85
-
如果WHERE子句的條件為真,則輸出x.Sname和x.Sdept
-
重復步驟2~4,繼續處理下一個元組,直到處理完Student表的所有元組
為了易于理解,可以使用表名作為元組變量名
謂詞IN
謂詞IN是二元運算符,一般書寫形式為A IN S
,A是一個列名,S是一個集合。如果A是集合S的元素,則結果為真,否則結果為假
例:查詢選修了1024號課程的學生的姓名和所在系
本例涉及Student表和SC表,分兩步構造。首先在SC表中查詢選修了1024號課程的學生集合,記為S
SELECT Sno
FROM SC
WHERE Cno = '1024';
然后對Student表的每個元組t,如果t.Sno∈S
成立,則t是查詢結果之一。使用IN代替∈,使用元組變量表示t,就得到了下面的SQL語句
SELECT t.Sname, t.Sdept
FROM Student t
WHERE t.Sno IN(SELECT SnoFROM SCWHERE Cno = '1024');
也可以直接用表名做為元組變量
SELECT Student.Sname, Student.Sdept
FROM Student
WHERE Student .Sno IN(SELECT SnoFROM SCWHERE Cno = '1024');
上述語句執行過程為:先執行子查詢,得到一個選了1024號課程的學生的集合,再執行外查詢,對Student表中的每個元組,測試元組在Sno列上的分量值是否在子查詢的結果中,如滿足則輸出這個元組
例:查詢選修了“管理學”的學生學號和姓名
選修了管理學的學生:
SELECT Sno
FROM SC, Course
WHERE SC.Cno = Course.Cno AND Course.Cname = '管理學';
判斷Student表中的每個元組在Sno上的分量是否在學生集合中,得:
SELECT Sno, Sname
FROM Student
WHERE Student.Sno IN(SELECT SnoFROM SC, CourseWHERE SC.Cno = Course.Cno AND Course.Cname = '管理學');
或
SELECT Sno, Sname
FROM Student
WHERE Sno IN(SELECT SnoFROM SCWHERE Cno IN(SELECT CnoFROM CourseWHERE Cname = '管理學'));
謂詞EXISTS
謂詞EXISTS是一元運算符,運算數是一個集合,如果該集合不是空集,則運算結果為真,否則運算結果為假。
例:查詢所有選修了1024號課程的學生的姓名
SELECT Sname
FROM Student
WHERE Sno IN (SELECT SnoFROM SCWHERE Cno='1024');
這是一個不相關嵌套查詢,也可以使用EXISTS謂詞實現
對Student表的任何一個元組x,如果選修了1024號課程,則SC表中存在x的選課記錄,該記錄在Sno列上的分量等于x.Sno,在Cno上的分量等于’1024’。因此,集合SELECT * FROM SC WHERE Sno = x.Sno AND Cno = '1024'
一定不是空集,則表達式EXISTS(SELECT * FROM SC WHERE Sno = x.Sno AND Cno = '1024)
為真;如果x沒有選修1024號課程,則表達式結果為假
SELECT x.Sname
FROM Student x
WHERE EXISTS(SELECT *FROM SCWHERE Sno = x.Sno AND Cno = '1024');
例:查詢至少選修了學號為2000014的學生所選修的全部課程的學生的姓名及所在系
用R表示學號為2000014的學生所選修的全部課程的集合,S表示學生x選修的全部課程的集合,如果R?S成立,則x是要查找的學生
SELECT x.Sname x.Sdept
FROM Student x
WHERE x.Sno!='2000014' ANDNOT EXISTS (SELECT t.CnoFROM SC tWHERE Sno='2000014' AND NOT EXISTS(SELECT CnoFROM SCWHERE Sno = x.Sno AND Cno=t.Cno));
例:查詢與學號為2000014的學生所選修的課程相同的學生的姓名
用R表示學號為2000014的學生選修的所有課程的集合,用S表示學生x選修的課程,如果R=S,則x是要查詢的學生
SELECT Student x
FROM Student x
WHERE x.Sno != '2000014' AND NOT EXISTS (SELECT Cno --R∈SFROM SC yWHERE Sno = '2000014' AND NOT EXISTS(SELECT CnoFROM SCWHERE Sno = x.Sno AND Cno = y.Cno))ANDNOT EXISTS (SELECT CnoFROM SC zWHERE Sno = x.Sno AND NOT EXISTS(SELECT CnoFROM SCWHERE Sno = '2000014' AND Cno = z.Cno));
FROM 子句中的子查詢
FROM子句可以指定查詢要使用的表。子查詢的結果是一個表,但只是一個中間結果,并沒有存放在數據庫。為了在FROM子句使用子查詢,要給子查詢生成的臨時表命名,有時還要命名臨時表的列
例:查詢每門課的名稱和平均成績
首先可以很容易的寫出查詢每門課的課程號和平均成績的SQL語句
SELECT Cno, AVG(Grade)
FROM SC
GROUP BY Cno;
為了得到課程的名字,將臨時表和Course表連接即可
SELECT Cname, Grade
FROM Course, (SELECT Cno, AVG(Grade)FROM SCGROUP BY Cno) AS tmp(Cno, Grade) --命名臨時表
WHERE Course.Cno = tmp.Cno;
也可以使用連接操作和分組操作實現
SELECT Cname, AVG(Grade)
FROM Course, SC
WHERE Course.Cno = SC.Cno
GROUP BY Cname;
外連接
使用條件連接運算時,只有滿足連接條件的元組才能作為查詢條件。假設A表和B表做條件連接,有時A表中會有某個元組t,由于在B表中沒有任何一個元組滿足與t的連接條件,因此t不會出現在連接結果中。
為了解決參與連接的表的某些元組沒有出現在連接結果中的問題,需要使用左外連接、右外連接和全外連接運算,作為區分,前面介紹的連接叫做內連接
左外連接
A表和B表做左外連接,其過程是先按照連接條件做連接運算,得到一個結果。如果A的某個元組t不在結果中,則將t和B的一個“萬能元組”做連接,這個萬能元組在所有列上取空值,即(NULL, NULL, …, NULL),形成一個新元組,加入最終結果。這個“萬能元組”并不真實存在
表達為
A LEFT OUTER JOIN B ON Condition
Course表和SC表做左外連接運算,物理課出現在查詢結果中
SELECT *
FROM Course LEFT OUTER JOIN SC ON Course.Cno = SC.Cno
右外連接
A表和B表做右外連接,其過程是先按照連接條件做連接運算,得到一個結果。如果B的某個元組t不在結果中,則將t和A的一個“萬能元組”做連接,形成一個新元組,加入最終結果。
表達為
A RIGHTOUTER JOIN B ON Condition
全外連接
全外連接是左外連接和右外連接的并
例:查詢每門課的選修人數
根據題意,查詢結果需要包括每門課程,所以使用左外連接,然后對查詢結果分組、統計
有兩種寫法:
SELECT Cname, COUNT(*)
FROM Course LEFT OUTER JOIN SC ON Course.Cno = SC.Cno
GROUP BY Cname;
SELECT Cname, COUNT(Sno)
FROM Course LEFT OUTER JOIN SC ON Course.Cno = SC.Cno
GROUP BY Cname;
COUNT(*)函數用于統計分組中的元組數,不會省略值為NULL的元組,所以盡管無人選修物理課,任然會計數1,這是錯誤的;而COUNT(Sno)
在計數時舍棄了NULL值,所以統計結果為0,是符合要求的
未完待續…