以初學者的角度理解:SQL實現關系除法
相信各位在學習SQL的時候,由於沒有一家SQL語言提供除法命令而只能自己寫一個。而網上大多就是四步驟加一個模板:
select distinct A.X
from A A1
where not exists(
select B.Y
from B
where not exists(
select *
from A A2
where A1.X = A2.X
and A2.Y = B.Y
)
)
那四個步驟又寫的過於抽象~,看得一頭霧水。因此筆者希望從一個初學者的角度,講解一下關系除法的實現過程,幫助大家理解。
例子
我們舉一個實例來講解~
我們用一張SC表和Course表,其中:
SC表:

Course表:

而我們想要做的是:
查詢選修了全部課程的學生姓名。
很明顯我們需要做除法:
select distinct SC1.Sno
from SC SC1
where not exists(
select Course.Cno
from Course
where not exists(
select *
from SC SC2
where SC1.Sno = SC2.Sno
and SC2.Cno = Course.Cno
)
)
接下來講解這段語句的整個過程~
具體講解
我們先看看這兩句SQL
select distinct SC1.Sno
from SC SC1
這一段,產生的結果應該是:

接下來到下一句:
where not exists()
這句話的意思很簡單,就是我即將進行括號中寫好的查詢語句,如果查詢結果為空,返回true,否則返回false。
這時候,我們開始where語句,此時SC1.Sno的值會被挨個放進where中進行處理。
此時:

接下來進入not exists的部分。
select Course.Cno
from Course
跟上面一樣,先看看它執行出來什么效果:

接下來到下一句:
where not exists()
此時:

接下來進入下一個not exists的部分。
select *
from SC SC2
where SC1.Sno = SC2.Sno
and SC2.Cno = Course.Cno
我們的SC表是這樣子的:

我們看看當select完之后,where在干嘛:
where SC1.Sno = SC2.Sno
and SC2.Cno = Course.Cno
這句話的意思是:
當
SC1.Sno與本關系中的Sno相等且Course.Cno與本關系中Cno的值相等
那回頭看看,我們進行的步驟,我們發現到這一步的時候,SC1.Sno = 20110001,而Course.Cno = 001。換言之,我們要找到符合這兩個條件的元組。
不難找到:

因此
select *
from SC SC2
where SC1.Sno = SC2.Sno
and SC2.Cno = Course.Cno
返回結果集:
20110001 001 89
這時候,我們看回上一層結構:
select Course.Cno
from Course
where not exists(...)
我們成功返回了not exists部分的結果集,因此where處得到的結果是false,因此Course.Cno = 001不被加入這層結構產生的結果集。所以指針往下,Course.Cno的數據變更:

Coures.Cno = 002
於是我們再次進入該語句:
select *
from SC SC2
where SC1.Sno = SC2.Sno
and SC2.Cno = Course.Cno
此時SC1.Sno = 20110001,而Course.Cno = 002。

返回結果集:
20110001 002 78
這時候,我們看回上一層結構:
select Course.Cno
from Course
where not exists(...)
我們成功返回了not exists部分的結果集,因此where處得到的結果是false,因此Course.Cno = 002不被加入這層結構產生的結果集。所以指針往下,Course.Cno的數據變更:

Coures.Cno = 003
於是我們再次進入該語句:
select *
from SC SC2
where SC1.Sno = SC2.Sno
and SC2.Cno = Course.Cno
此時SC1.Sno = 20110001,而Course.Cno = 003。

返回結果集:
20110001 003 89
這時候,我們看回上一層結構:
select Course.Cno
from Course
where not exists(...)
我們成功返回了not exists部分的結果集,因此where處得到的結果是false,因此Course.Cno = 002不被加入這層結構產生的結果集。所以指針往下,Course.Cno的數據變更:
Coures.Cno = 004
重復上述的操作,不再贅述。
我們發現,由於20110001這位同學剛剛好都把課選了。每次我們都能從SC表中找到數據,因此
select Course.Cno
from Course
where not exists(...)
該段語句最終的結果是一個空集,沒有一個數據被放進了結果集。
這時候我們再回到上一層結構:
select distinct SC1.Sno
from SC SC1
where not exists(...)
該處的not exists中的查詢語句返回的是空集,所以它返回的是true。
也就是說,where處得到的結果是true。
也就是說,Sno = 20110001 被加入結果集。
我們繼續分析,想要Sno不被加入結果集:
select Course.Cno
from Course
where not exists(...)
這段語句就需要返回結果集。
也就是說這段語句not exists部分至少有一個返回true。
那么這段語句:
select *
from SC SC2
where SC1.Sno = SC2.Sno
and SC2.Cno = Course.Cno
只要元祖沒有被找到(即有一門課該學生沒有選),它就返回空集。
接下來一系列的連鎖反應:
select Course.Cno
from Course
where not exists(...)
返回一個非空結果集
select distinct SC1.Sno
from SC SC1
where not exists(...)
由於返回的是true,所以該學生的學號不被加入結果集。
總結一下
select distinct A.X
from A A1
where not exists(
select B.Y
from B
where not exists(
select *
from A A2
where A1.X = A2.X
and A2.Y = B.Y
)
)
實際上,直觀來看:
select distinct A.X
from A A1
where not exists()
這一段相當於按X分組。
select B.Y
from B
where not exists(
select *
from A A2
where A1.X = A2.X
and A2.Y = B.Y
)
這段就是分組來看,去確定在一組中,是不是有某一個數據沒有沒有在B.Y中,如果有,返回沒有在B.Y中的結果
如果的確有一個數據沒在其中,由於它是嵌套在not exists中的,只要它返回沒有在B.Y中的結果不是空集,就說明該組不合要求,不加入結果集。如果是空集,說明符合要求,加入結果集。
瞎說一下
我知道非常繞,靜下心按流程走一走,還是看不懂可以留言喔~
