前言
sql的嵌套查詢可以說是sql語句中比較復雜的一部分,但是掌握好了的話就可以提高查詢效率。下面將介紹帶in
的子查詢、帶比較運算符
的子查詢、帶any/all
的子查詢、帶exists
的子查詢以及基於派生表
的子查詢。很多數據庫是不區分關鍵字大小寫的,並且關鍵字還會有高亮,所以我為了寫語句方便(不要頻繁切換大小寫或者按shift鍵)和看着方便(大寫一般還要在大腦中轉換下)關鍵字是使用小寫。
什么是SQL嵌套查詢
嵌套查詢指的是一個查詢語塊可以嵌套在另外一個查詢語句塊的where子句或者having子句中,前者為子查詢或內查詢,后者為父查詢或外查詢。
表的定義
例子使用的表的定義為:
create table `student`(
`sno` char(12) collate utf8_bin not null primary key comment '學號',
`sname` char(30) collate utf8_bin not null comment '姓名',
`birthday` Date collate utf8_bin comment '出生日期'
)engine=InnoDB default charset=utf8 collate=utf8_bin comment '學號信息表';
create table `course`
(
`cno` char(4) collate utf8_bin not null primary key comment '課程號',
`cname` char(40) collate utf8_bin not null comment '課程名',
`ceredit` smallint not null default 0 comment '學分'
)engine=InnoDB default charset=utf8 collate=utf8_bin comment '課程表';
create table `sc`
(
`sno` char(12) collate utf8_bin not null comment '學號',
`cno` char(4) collate utf8_bin not null comment '課程號',
`score` smallint not null default 0 comment '成績'
)engine=InnoDB default charset=utf8 collate=utf8_bin comment '學生課程表';
#為sc表添加主鍵和外鍵
alter table `sc` add primary key (`sno`,`cno`);
alter table `sc` add foreign key(`sno`) references `student`(`sno`);
alter table `sc` add foreign key(`cno`) references `course`(`cno`);
帶in的子查詢
in關鍵字主要用於判斷表達式是否在多值列表中。返回在多值列表中的記錄。
#列出選修了C001課程的學生的學號、姓名
select sno, sname
from student
where sno in (select sno from sc where cno='C001');
這里子查詢里面沒有依賴父查詢,此種查詢也叫做不相關子查詢
。
若子查詢條件依賴於父查詢,則為相關子查詢
。
帶比較運算符的子查詢
帶比較運算符的子查詢指父查詢與子查詢之間通過比較運算符連接,並且子查詢返回的是單值,才可以用 = 、<、 >、 != 、>=、 <=等比較運算符連接。
#選出學號為2016110129的同學所選課程中的成績大於他平均成績的課程的課程號
select cno from sc as x
where score >
(
select avg(score) from sc as y where x.sno=y.sno and x.sno = '2016110129'
)
and sno = '2016110129';
這個子查詢依賴於父查詢,屬於相關子查詢。
因為這里將同一張表既作為父查詢的表又作為子查詢的表,所以將這張表取了兩個別名,以便區分。
【查詢過程】:將父查詢中的sno代入子查詢中sno進行匹配,然后判斷該記錄中的課程成績是否大於該學生的平均成績,符合條件則返回該記錄,否則繼續在子查詢中匹配該學生的下一條記錄。
帶any(some)或all的子查詢
子查詢返回單值時的比較,可以用上面的比較運算符,當返回多值時需要比較,就要使用any(some)或者all。
若是在與多值序列的比較中,只需要滿足與多值序列中的一個值滿足比較關系就返回true,則用any(some)。
若是在與多值序列的比較中,需要滿足與多值序列中的全部值滿足比較關系才返回true,則用all。
#查詢選課人數最多的課程號
select cno from sc
group by cno
having COUNT(*) >= all(select COUNT(*) from sc group by cno);
【查詢過程】:將sc表中的記錄按照cno進行分組,篩選記錄數最多的課程號。
all(select COUNT(*) from sc group by cno)
是找出所有以cno分組的記錄數,是一個多值集合。使用>=也就是選出最大的值。
帶exists的子查詢
exists代表存在量詞,帶有EXISTS的子查詢不返回任何數據,只產生邏輯真值“true”或者邏輯假值“false”。
使用exists的嵌套語句,若子查詢結果不為空,則exists返回true,否則返回false。
使用exists引出的子查詢,其目標表達式列都使用*,因為帶exists的子查詢只返回真值或假值,給出列名無實際含義。
#列出選修了C001課程的學生的學號、姓名
select sno,sname from student
where exists(
select * from sc where sc.sno=student.sno and cno='C001'
);
【查詢過程】:從student的第一條記錄開始查詢,將第一條記錄代入子查詢中,在sc表中匹配該學生選課記錄,若匹配到則立刻返回真,父查詢中輸出該記錄;若匹配完后結果仍為空,否則返回假,繼續父查詢繼續代入下一條記錄到子查詢中查詢。
與in子查詢的區別:
在帶in的子查詢中,會遍歷sc表中所有記錄進行篩選,帶exists的查詢找到一條記錄就返回,不會遍歷整個表,所以帶EXISTS的查詢是一個優質查詢。
附加一題作為exists的練習
“查詢選了所有課程的學生”
這里需要使用雙層帶not exists(即不存在)關鍵詞的查詢。具體查詢語句如下:
#查詢選了所有課程的學生
select sno,sname from student where not exists(
select * from course where not exists (
select * from sc where sc.sno=student.sno and sc.cno=course.cno
)
);
這個相當於一個進行一個雙重循環,因為是選出學生的信息,所以student表作為“外層循環”,course表作為“內循環”,在sc表中查詢學生的選課記錄是否存在。
把student表中第一個學生代入“內循環”,然后開始,在sc表中查詢該學生是否選了course表中所有課程。
如果遍歷了course表后,不存在沒有被選的課程(課程在sc表中沒有記錄),則說明該學生選了所有課程,內部not exists就會返回假,外部not exists返回為真,說明該學生不存在沒有選的課程,外部查詢輸出該學生的信息,然后開始下一個學生的查詢。
在遍歷course表時,若有一個課程沒有被選,則內部就會立刻返回真(不會繼續看下一門課程是否被選),外部查詢返回為假,說明該學生沒有選完所有課程,外部查詢就會開始下一個學生的查詢。
再附加一個練習
“找出被所有學生選了的課程的課程號和課程名”
select cno,cname from course where not exists(
select * from student where not exists (
select * from sc where sc.sno=student.sno and sc.cno=course.cno
)
);
基於派生表的查詢
select 查詢的結果也是一張表,可以作為出現在from子句后面作為派生表進行查詢。
#求學分獲得8分以上學生的學號 平均分 以及總學分
#需要注意此處的作用域不同,只有該課程的成績大於60才會獲得該課程的學分,平均分包括了所有課程(不及格和及格)
#思路:先將查詢到的總學分結果看做是一張表 再與sc表連接進行查詢平均分
select sc.sno,total_cre,avg(score) from
(select sno,SUM(ceredit) as total_cre from sc,course where sc.cno=course.cno
and score >= 60
group by sno
having SUM(ceredit) >= 8) as temptable, sc
where temptable.sno=sc.sno
group by sc.sno,sum_cre;
小結
回顧了學習數據庫時的嵌套查詢,簡單總結了一番。當時覺得其中比較難理解的exists查詢,現在梳理了一下理解起來也沒有那么困難。sql語句還是要常寫,不然一些邏輯復雜點的語句就會理解不到。