DB中有三個表,如圖:
Student表:
Course表:
SC表:(選課表)
首先看一個相對簡單的查詢:
題目1:查詢學生200215122未選擇的所有課程號。
我們的解決思路如下:
1、 對course表,取出第1行的cno
2、 (這一步由子查詢做)然后與sc表的每行逐一對比,看是那些使得sno=’200215122’的行的cno是否等於從course取出的cno,若相等,則將這一
cno放入結果表1中,從而結果表1不為空,not exits返回假,那么從第1行取出的這個cno則不放入最終結果表中。若不相等,則結果表1為空,從而not exists為真,從第1行取出的這個cno則放入最終結果表中。
3、 繼續1,2步,考察course中剩余的行,直到將course表中所有行,即所有課程考察完。
SELECT cno FROM course WHERE NOT EXISTS ( SELECT cno FROM sc WHERE sno='200215122' AND cno=course.cno ) |
注意:將not exists 換成 cno not in也可(不這樣具體的執行過程就不太一樣了),另外對於存在謂詞子查詢而言,選擇哪一列是無關緊要的,本例中子查詢我們寫的是“select cno from sc”,其實寫成“select * from sc”亦可,前面的not exists的真假值只與結果表1是否為空有關。還要注意,在考察course表時,每行都會在子查詢過程中生成自己的結果表1,該表只是外面not exists判斷每個cno是否放到最終結果表的依據。
另外要注意上述的子查詢是相關子查詢,因為子查詢的條件依賴於父查詢,即子查詢用來判斷cno=course.cno的左邊的cno由父查詢傳遞而來,下面的偽碼能清晰的表明這個意思。
將每個表都看成數組,將表中的每行看成一個對象,用偽代碼表達上述查詢的執行過程:
偽碼1:
ResultTable rt; for(int i=0;i<course.length;++i) { TempResultTable trt; for(int j=0;j<sc.length;++j) { if(sc[j].sno='200215122' && course[i].cno==sc[j].cno) { trt.add(sc[j].cno); /*每門課只能被一個學生選一次,這里應退出循環*/ break; } } //若空,說明course[i].cno這門課沒被200215122選上 //而沒被選擇上的課當放入最終的結果表rt中 if(trt.isEmpty()) { rt.add(course[i].cno); } } |
題目2:查詢學生200215122選擇的所有課程。
參考上面,不難寫出相應的sql語句:
SELECT cno FROM course WHERE EXISTS ( SELECT cno FROM sc WHERE sno='200215122' AND cno=course.cno ) |
/*這是更為簡單的查詢方式,因為所有被選中的課程 的課程號都在sc表中,而之所以題目1使用的方式較為復雜,是因為當我們考察未被選中的課程的時候,必須要考察course表,因為未被選中的課程在該表中才能找到,畢竟該表保存所有課程的記錄*/ select cno from sc where sno='200215122'
|
參考偽碼1不難分析出題目2第一種查詢方式的偽碼。
題目3:查詢沒有選課的學生。
這樣的學生符合:用其學號考察選課表試圖得到其選課表時,發現該選課表為空。
select sno from s where not exists ( select cno from sc where sno=sc.sno ) |
其實就是選出所有這樣的學號,使得子查詢為空。其具體執行過程可以參照偽碼1。
題目4:查詢沒有選擇全部課程的學生。
SELECT cno FROM course WHERE NOT EXISTS ( SELECT cno FROM sc WHERE sno='200215122' AND cno=course.cno ) |
左邊是題目1的sql語句,我們知道,其返回的是200215122未選擇的課程號集合,如果該集合不為空,我們就知道200215122便是沒有選擇全部課程的學生之一,這是對200215122的考察,我們要考察全體學生,就不能以一個具體的學號來考察,學生表中的學號當作為變量傳遞給子查詢中(外層for循環傳遞給內層for循環)。
SELECT sno FROM student WHERE EXISTS ( SELECT cno FROM course WHERE NOT EXISTS ( SELECT cno FROM sc WHERE sno=student.sno AND cno=course.cno ) ) |
具體分析:左邊sql執行過程是這樣的
1、外層for循環開始,從student表中取出第1個學號sno1
2、進入次外層for循環,從course表中取出第一個課號cno1
3、內層for循環用sno1和cno1去匹配sc表中的每行,若匹配上,則sno1選擇了cno1,回到次外層循環,考察sno1是否選擇了cno2,依次類推,直到將所有course表中的cno考察完。在為sno1考察course中的cno時,若在內層循環中,沒有與sc中的任一行匹配上,則說明該生sno1沒有選擇該課程cno。那么將該生cno1放入最終結果表中。
4、重復上面的步驟考察sno2……直到學生表的所有學號都被考察過。
可見,上面的執行過程本質是三層循環。畢竟計算機要看某門課程是否被某學生選中或着不選中,它只能(sno,cno)去選擇表中一一匹配,別無它法——事實上,讓人來完成這件事情不也是這樣做么。而exists和not exist的區別僅僅在於:若子查詢生成的集合為空,前者返回假,后者返回真,反之類同。在考察父表中某一行時,如果謂詞返回真,說明父查詢在執行過程中的某一行,符合條件,該行便會放到最終結果表(集合)中。
題目5:查詢選擇了全部課程的學生。
有了上面的基礎,這個就不是很難寫了:
SELECT sno FROM student WHERE NOT EXISTS ( SELECT cno FROM course WHERE NOT EXISTS ( SELECT cno FROM sc WHERE sno=student.sno AND cno=course.cno ) ) |
如果沒有計算機,我們得自己動手來完成這個功能,我們會這樣做:因為要考察所有學生,所以需要student表,又因為要考察所有課程所以需要課程表,又因為要考察選課情況,所以需要選課表。現在三個表都有了,開始考察吧。從student表中取出sno1,從course表中取出cno1,看看(sno1,cno1)是否在sc中……