數(shù)據(jù)庫中,如果用SQL語言進行多個表的查詢,就需要用到連接操作。這里對SQL中的連接類型進行簡單的介紹。本文內(nèi)容可能會涉及以下幾張表:
院系表:department(dept_name, building, budget);
課程表:course(course_id, title, dept_name, credits)
老師表:instructor(ID, name, dept_name, salary)
教學(xué)表:teaches(ID, course_id, sec_id, semester)
學(xué)生表:student(ID, name, dept_name, tot_cred)
選課表:takes(ID, course_id, sec_id, semester, year, grade)
1、多表查詢
如果要查詢所有教師的姓名、院系和院系所在的建筑,就需要用到多表查詢:
select name, instructor.dept_name, building
from instructor, department
where instructor.dept_name = department.dept_name
可以這樣理解,該查詢在執(zhí)行時,首先將instructor表和department表中的所有的記錄做笛卡爾積,然后,根據(jù)where子句中指定的謂詞,對笛卡爾乘積的結(jié)果進行篩選,最后得到查詢結(jié)果。
然而,實際中,該查詢不會按照這個步驟執(zhí)行,數(shù)據(jù)庫會(盡可能地)只產(chǎn)生滿足where子句謂詞的笛卡爾積元素來進行優(yōu)化執(zhí)行。
這是SQL種最基本的表連接形式。除此之外,SQL中還提供了關(guān)鍵字join,專門用于表連接查詢,這些連接可以分為內(nèi)連接(inner join)和外連接(outer join)兩種。外連接又分為左外連接(left outer join)、右外連接(right outer join)和全外連接(full outer join)。
2、自然連接
2.1 什么是自然連接
這里首先介紹自然連接,看下面的連接:
select name, instructor.dept_name, building
from instructor, department
where instructor.dept_name = department.dept_name
這里的連接謂詞是instructor.dept_name = department.dept_name
,而這兩個表中名稱相同的字段只有dept_name。實際上,這是一種常見的情況。為此,SQL支持了一種叫做自然連接(natural join)的運算。
自然連接只取出兩張表的笛卡爾乘積中,那些在兩個表中都出現(xiàn)的字段上取值相同的結(jié)果。比如:
select name, instructor.dept_name, building
from instructor natural join department
這個例子中,只會取出instructor表和department表笛卡爾乘積結(jié)果中,那些instructor.dept_name和department.dept_name相等的記錄,作為連接的結(jié)果。因為dept_name是唯一的兩張表中都出現(xiàn)的字段(如果兩張表還有其他相同的字段,那么查詢結(jié)果中來自不同表的這些字段也都必須相等)。
2.2 自然連接注意
除了只關(guān)注兩個表中的相同字段,自然連接還有兩個地方需要注意:
連接結(jié)果中,對于兩張表中都有的字段,只出現(xiàn)一次。
-
字段出現(xiàn)的順序
先是兩張表中都有的字段,然后是只出現(xiàn)在第一張表中的字段,最后是只出現(xiàn)在第二張表中的字段。
2.3 自然連接運算的結(jié)果是關(guān)系
關(guān)系數(shù)據(jù)庫中,關(guān)系就是表,表就代表關(guān)系。這里,自然連接運算的結(jié)果是關(guān)系,可以理解為自然連接的結(jié)果也是一張表。例子,查詢所有教師的名字,以及他們教授的課程名稱。
select name, title
from instructor natural join teaches, course
where teaches.course_id = course.course_id
這個例子中,先將instructor表和teaches表進行自然連接,由于得到的結(jié)果也是一個關(guān)系(表),所以也可以將該結(jié)果和course表連接。注意,這里的teaches.course_id是自然連接結(jié)果中的course_id字段,因為它是來自teaches表,所以用teaches.course_id
標識。
2.4 using子句
看上面的連接語句,似乎可以寫成:
select name, title
from instructor natural join teaches natural join course
其實,前一個自然連接的結(jié)果包括字段(ID, name, dept_name, salary, course_id, sec_id),而course表包含的字段(course_id, title, dept_name, credits)。兩者的公共字段除了course_id之外,還有dept_name。所以,該SQL實際上相當于:
select name, title
from instructor natural join teaches, course
where teaches.course_id = course.course_id and teaches.dept_name = course.dept_name
為了避免不必要的相等屬性帶來的問題,同時發(fā)揚自然連接的優(yōu)點,SQL中提供了using子句來允許用戶指定哪些字段相等。例如,上面的SQL就相當于:
select name, title
from instructor natural join teaches natural join course using (course_id, dept_name)
當然,using子句中也可以是單個字段,如:
select name, title
from instructor natural join teaches natural join course using (course_id)
2.5 on子句
on子句允許在參與連接的關(guān)系上指定通用的謂詞,這個謂詞的寫法和where子句謂詞類似。和using子句一樣,on子句出現(xiàn)在連接表達式的末尾。下面是一個例子:
select *
from student join takes on student.ID = takes.ID
這里的意思是連接student表和takes表中,ID字段值相同的記錄。功能上和下面的腳本類似:
select *
from student natural join takes
兩者之間的區(qū)別就在于前者結(jié)果中ID屬性出現(xiàn)兩次,一次來自student,一次來自takes;而后者結(jié)果中ID屬性只出現(xiàn)一次,因為是自然連接。
on條件可以表示任何的SQL謂詞,因此使用on條件的連接表達式可以表示比自然連接更加復(fù)雜的連接條件。功能上,上面帶on子句的連接等價于:
select *
from student, takes
where student.ID = takes.ID
因此,可以將on子句中的謂詞移到where子句中,但這并不是說on子句是多余的,使用on子句有以下優(yōu)點:
- 在外連接中,on子句和where子句的表現(xiàn)不同(后文介紹)。
- 在on子句中指定連接條件,在where子句中出現(xiàn)其它的連接條件,這樣的SQL可讀性更好。
3、外連接
3.1 外連接介紹
看如下查詢:
select *
from student natural join takes
可以查詢出學(xué)生信息和每個學(xué)生的選課信息。但是,這里有個問題,如果有一名學(xué)生Snow沒有選任何課程,那么他不會出現(xiàn)在查詢結(jié)果中。為了避免參與連接的一個或兩個表中的某些記錄以這種方式“丟失”,SQL提供了外連接操作。通過在結(jié)果中創(chuàng)建包含空值記錄的方式,保留那些在連接中“丟失”的記錄。
SQL中的外連接有三種形式:
- 左外連接(left outer join)
保證出現(xiàn)在連接操作左邊的表記錄不“丟失” - 右外連接(right outer join)
保證出現(xiàn)在連接操作右邊的表記錄不“丟失” - 全外連接(full outer join)
保證出現(xiàn)在連接操作兩邊的表記錄都不“丟失”
比如,上面的例子中,為了保證學(xué)生記錄不丟失,可以采用左外連接:
select *
from student natural left join takes
這樣,查詢結(jié)果中就會多一條記錄,該記錄中來自student表中的字段就是student表中Snow記錄的值,而來自takes表中的字段都為null。
3.2 利用外連接查詢“丟失”的記錄
這樣,利用外連接,可以查詢出一門課都沒有選的學(xué)生:
select ID
from student natural left join takes
where course_id is null
注意:判斷是否為空用的是is null
。
3.3 左外連接和右外連接是對稱的
不難理解,如下的兩個連接基本等價:
select *
from student natural left join takes
基本等價于
select *
from takes natural left join student
這里,基本等價的意思是說,結(jié)果中屬性出現(xiàn)的順序會不同。
3.4 on子句和外連接
on子句也可以和外連接一起使用,下面兩個查詢的功能基本相同(除了結(jié)果中ID字段出現(xiàn)的次數(shù)):
select *
from student left outer join takes on student.ID = takes.ID
基本等價于
select *
from student left natural join takes
如前所述,外連接中on子句的表現(xiàn)和where子句不同,因為外連接只為結(jié)果集中沒有出現(xiàn)的記錄補上空值并插入結(jié)果集,on子句是外連接聲明的一部分。但where子句卻不是,where子句中的謂詞作用于外連接的結(jié)果,用來對外連接的結(jié)果進行篩選。看下面的查詢:
select *
from student left outer join takes on true
where student.ID = takes.ID
在這個查詢中,由于外連接中on子句的謂詞用的是true,所以student和takes表進行外連接實際上相當于做了笛卡爾積,在該外連接的結(jié)果中,包含所有來自兩個表的記錄,所以,不會向連接結(jié)果中補充空值記錄。而where子句只篩選出外連接結(jié)果中滿足謂詞條件的記錄。這樣,學(xué)生Snow這樣的記錄就不會出現(xiàn)在最后的結(jié)果中。這就是外連接中on和where的區(qū)別。
4、內(nèi)連接
區(qū)別于上面提到的外連接,SQL把一般的連接稱作內(nèi)連接(inner join)。而關(guān)鍵字inner是可選的,如果join子句中沒有outer前綴,那么默認的連接類型就是inner join。
select *
from student join takes using(ID)
等價于
select *
from student inner join takes using(ID)
類似地,natural join等價于natural inner join。
5、總結(jié)
實際上,內(nèi)連接和外連接可以歸納為連接的類型,而natural、on子句和using子句可以歸納為連接的條件。如下表:
連接類型 | 連接條件 |
---|---|
inner join | natural |
left outer join | on子句 |
right outer join | using子句 |
full outer join | ... |
任意的連接類型可以和任意的連接條件,進行組合,來滿足連接查詢需求。
參考
《數(shù)據(jù)庫系統(tǒng)概念》 機械工業(yè)出版社 Abraham Silberschatz, Henry F. Korth, S. Sudarshan 楊冬青, 李紅燕, 唐世渭等譯。