關於謂詞查詢的深入分析(select …where (not) exists (…))


DB中有三個表,如圖:

Student表:

clip_image002

Course表:

clip_image004

SC表:(選課表)

clip_image006

首先看一個相對簡單的查詢:

題目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中……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM