原文地址:http://sunstring.blog.163.com/blog/static/210478165201342943836560/
本篇文章中,主要說明SQL中的各種連接以及使用範圍,以及更進一步的解釋關係代數法和關係演算法對在同一條查詢的不同思路。
多表連接簡介
在關係數據庫中,一個查詢往往會涉及多個表,因爲很少有數據庫只有一個表,而如果大多查詢只涉及到一個表的,那麼那個表也往往低於第三範式,存在大量冗餘和異常。
因此,連接(Join)就是一種把多個表連接成一個表的重要手段.
比如簡單兩個表連接學生表(Student)和班級(Class)表,如圖:
進行連接後如圖:
笛卡爾積
笛卡爾積在SQL中的實現方式既是交叉連接(Cross Join)。所有連接方式都會先生成臨時笛卡爾積表,笛卡爾積是關係代數裏的一個概念,表示兩個表中的每一行數據任意組合,上圖中兩個表連接即爲笛卡爾積(交叉連接)
在實際應用中,笛卡爾積本身大多沒有什麼實際用處,只有在兩個表連接時加上限制條件,纔會有實際意義,下面看內連接
內連接
如果分步驟理解的話,內連接可以看做先對兩個表進行了交叉連接後,再通過加上限制條件(SQL中通過關鍵字on)剔除不符合條件的行的子集,得到的結果就是內連接了.上面的圖中,如果我加上限制條件
對於開篇中的兩個表,假使查詢語句如下:
SELECT * FROM [Class] c inner join [Student] s on c.ClassID=s.StudentClassID
可以將上面查詢語句進行分部理解,首先先將Class表和Student表進行交叉連接,生成如下表:
然後通過on後面的限制條件,只選擇那些StudentClassID和ClassID相等的列(上圖中劃了綠色的部分),最終,得到選擇後的表的子集
當然,內連接on後面的限制條件不僅僅是等號,還可以使用比較運算符,包括了>(大於)、>=(大於或等於)、<=(小於或等於)、<(小於)、!>(不大於)、!<(不小於)和<>(不等於)。當然,限制條件所涉及的兩個列的數據類型必須匹配.
對於上面的查詢語句,如果將on後面限制條件由等於改爲大於:
SELECT * FROM [Class] c inner join [Student] s on c.ClassID>s.StudentClassID
則結果從第一步的笛卡爾積中篩選出那些ClassID大於StudentClassID的子集:
雖然上面連接後的表並沒有什麼實際意義,但這裏僅僅作爲DEMO使用:-)
關係演算
上面笛卡爾積的概念是關係代數中的概念,而我在前一篇文章中提到還有關係演算的查詢方法.上面的關係代數是分佈理解的,上面的語句推導過程是這樣的:“對錶Student和Class進行內連接,匹配所有ClassID和StudentClassID相等行,選擇所有的列”
而關係演算法,更多關注的是我想要什麼,比如說上面同樣查詢,用關係演算法思考的方式是“給我找到所有學生的信息,包括他們的班級信息,班級ID,學生ID,學生姓名”
用關係演算法的SQL查詢語句如下:
SELECT * FROM [Class] c , [Student] s where c.ClassID=s.StudentClassID
當然,查詢後返回的結果是不會變的:
外連接
假設還是上面兩個表,學生和班級.我在學生中添加一個名爲Eric的學生,但出於某種原因忘了填寫它的班級ID:
當我想執行這樣一條查詢:給我取得所有學生的姓名和他們所屬的班級:
SELECT s.StudentName,c.ClassName FROM [fordemo].[dbo].[Student] s inner join [fordemo].[dbo].[Class] c on s.StudentClassID=c.ClassID
結果如下圖:
可以看到,這個查詢“丟失”了Eric..
這時就需要用到外連接,外連接可以使連接表的一方,或者雙方不必遵守on後面的連接限制條件.這裏把上面的查詢語句中的inner join改爲left outer join:
SELECT s.StudentName,c.ClassName FROM [fordemo].[dbo].[Student] s left outer join [fordemo].[dbo].[Class] c on s.StudentClassID=c.ClassID
結果如下:
Eric又重新出現.
右外連接
右外連接和左外連接的概念是相同的,只是順序不同,對於上面查詢語句,也可以改成:
SELECT s.StudentName,c.ClassName FROM [fordemo].[dbo].[Class] c right outer join [fordemo].[dbo].[Student] s on s.StudentClassID=c.ClassID
效果和上面使用了左外連接的效果是一樣的.
全外連接
全外連接是將左邊和右邊表每行都至少輸出一次,用關鍵字”full outer join”進行連接,可以看作是左外連接和右外連接的結合.
自連接
談到自連接,讓我們首先從一個表和一個問題開始:
上面員工表(Employee),因爲經理也是員工的一種,所以將兩種人放入一個表中,MangerID字段表示的是當前員工的直系經理的員工id.
現在,我的問題是,如何查找CareySon的經理的姓名?
可以看出,雖然數據存儲在單張表中,但除了嵌套查詢(這個會在後續文章中講到),只有自連接可以做到.正確自連接語句如下:
SELECT m.EmployeeName FROM [fordemo].[dbo].[Employee] e inner join [fordemo].[dbo].[Employee] m on e.ManagerID=m.id and e.EmployeeName='Careyson'
在詳細解釋自連接的概念之前,請再看一個更能說明自連接應用之處的例子:
這個表是一個出席會議記錄的表,每一行表示出席會議的記錄(這裏,由於表簡單,我就不用EmployeeID和MeetingID來表示了,用名稱對於理解表更容易些)
好了,現在我的問題是:找出既參加“談論項目進度”會議,又參加”討論職業發展”會議的員工
乍一看上去很讓人迷惑是吧,也許你看到這一句腦中第一印象會是:
SELECT EmployeeName FROM [fordemo].[dbo].[MeettingRecord] m where MeetingName='¨???????????¨¨' and meetingName='¨???????¨°???¤?é?1'
(我用的代碼高亮插件不支持中文,所以上面where子句後面第一個字符串是’談論項目進度’,第二個是’討論職業發展’)
恩,恭喜你,答錯了…如果這樣寫將會什麼數據也得不到.正確的寫法是使用自連接!
自連接的是一種特殊的連接,是對物理上相同但邏輯上不相同的表進行連接的方式。我看到百度百科上說自連接是一種特殊的內連接,但這是錯誤的,因爲兩個相同表之間不光可以內連接,還可以外連接,交叉連接…在進行自連接時,必須爲其中至少一個表指定別名以對這兩個表進行區分!
回到上面的例子,使用自連接,則正確的寫法爲:
SELECT m.EmployeeName FROM [fordemo].[dbo].[MeettingRecord] m, [fordemo].[dbo].[MeettingRecord] m2 where m.MeetingName='¨???????????¨¨' and m2.MeetingName='¨???????¨°???¤?é?1' and m.EmployeeName=m2.EmployeeName
(關於亂碼問題,請參考上面)
多表連接
多個表連接實際上可以看成是對N個表進行n-1次雙表連接.這樣理解會讓問題簡單很多!
比如上面三個表,前兩個表是我們已經在文章開始認識的,假設現在又添加了一個教師表,對這三個表進行笛卡爾積如下:
SELECT * FROM [fordemo].[dbo].[Class] cross join [fordemo].[dbo].[Teacher] cross join [fordemo].[dbo].[Student]
結果可以如圖表示:
總結
文中對SQL中各種連接查詢方式都做了簡單的介紹,並利用一些Demo實際探討各種連接的用處,掌握好各種連接的原理是寫好SQL查詢所必不可少的!
-------------------------------------------------------------
沒有join條件導致笛卡爾乘積
學過線性代數的人都知道,笛卡爾乘積通俗的說,就是兩個集合中的每一個成員,都與對方集合中的任意一個成員有關聯。可以想象,在SQL查詢中,如果對兩張表join查詢而沒有join條件時,就會產生笛卡爾乘積。這就是我們的笛卡爾乘積導致的性能問題中最常見的案例:開發人員在寫代碼時遺漏了join條件。
發生笛卡爾乘積的sql:
view plaincopy to clipboardprint?select sum(project_fj.danjia*project_fj.mianji) from project_fj,orderform where project_fj.zhuangtai='未售' and project_fj.project_id=30
select sum(project_fj.danjia*project_fj.mianji) from project_fj,orderform where project_fj.zhuangtai='未售' and project_fj.project_id=30
這個語句其實只是sql語句的一部分,問題是另一部分用到了表orderform,所以from中有orderform,但是上面的這部分語句完全沒有用到orderform,但是不設置條件就導致了笛卡爾乘積。
解決方法:使用LEFT JOIN
view plaincopy to clipboardprint?select sum(project_fj.danjia*project_fj.mianji) from project_fj LEFT JOIN orderform ON project_fj.id=orderform.project_id
where project_fj.zhuangtai='未售' and project_fj.project_id=30
select sum(project_fj.danjia*project_fj.mianji) from project_fj LEFT JOIN orderform ON project_fj.id=orderform.project_id
where project_fj.zhuangtai='未售' and project_fj.project_id=30
本文出自“suixufeng的專欄”