【Oracle】深入多表連接

日常工作中,爲滿足業務需求,要寫個多表連接查詢SQL,對大多數SQLer來說是很容易的,多表連接查詢的語法如下:

select table1.column,table2.column
from table1 [inner | left | right | full ] join table2 on table1.column1 = table2.column2;

inner join 表示內連接;
left join表示左外連接;
right join表示右外連接;
full join表示完全外連接;
on子句 用於指定連接條件。

根據這個語法,運行基本上就查詢出需要的數據,如果效率較低,還需要做個語句優化,然後就可以交差的,但是你是否曾經有過疑問呢?
1,on 子句中的連接條件只能使用‘=’嗎?除此之外還能不能使用其他比較運算?比如:<、<=、>、>=
2,on 子句其實有着過濾記錄的功能,那麼on 子句 和 where 子句 又有什麼區別?
3,表連接類型有內連接,外鏈接,那們在數據庫中是如何實現關聯?

一,on 子句中的連接條件只能使用‘=’嗎?除此之外還能不能使用其他比較運算?比如:<、<=、>、>=

on 子句 中不僅可以使用等值連接,還是可以使用非等值連接,其實on 子句就是個連接條件,table1 和 table2 中滿足這個條件的記錄就可以關聯起來。平時的實際應用中,表之間更多的是靠主鍵-外鍵的形式關聯,所以更多的使用等值連接,而且用於關聯的字段通常都建立索引,非等值連接中的 <、<=、>、>= 會導致索引失效,影響效率。

例1:匹配每個員工的薪資等級
salgrade 中有每個等級的薪資範圍。
在這裏插入圖片描述

select e.* ,s.grade from EMP e
left join salgrade s on  e.sal between s.losal and s.hisal  --根據範圍劃分等級,

在這裏插入圖片描述
例2:SQL取第K大值
這個問題其實直接使用分析函數就可以快速查詢出來,但是如果限制使用分析函數,比如:MySQL的版本<8.0還不支持分析函數,這時就可以使用自連接來實現。

SELECT 
*
FROM 
sc
WHERE score = 
(SELECT
t1.score
FROM sc t1
JOIN sc t2 ON t1.score <= t2.score    
GROUP BY t1.score
HAVING COUNT(DISTINCT t2.score) = 2)   -- 以取第二大的爲例子

原理很簡單,通過自連接找到大於等於當前記錄的記錄數n,當 n=k時,則該條記錄就是需要查找的第k大值;
可以參考博文:https://blog.csdn.net/yangjjuan/article/details/104905052

二,on 子句其實有着過濾記錄的功能,那麼on 子句 和 where 子句 又有什麼區別?

其實SQL和其他編程語言一樣,都有自己的一套語法標準,比如SQL89、SQL92、SQL99,每種標準的語法就有所不同,具體大家可以找找看,不過Oracle 好像都支持。

在SQL92多個查詢的表在from後邊,使用逗號連接查詢條件在where後邊,多個連接條件使用and連接,並且帶上過濾條件,也是說where 子句 承包了連接條件 和 篩選條件
在SQL92 中的 外連接是如下:不帶(+) 的爲主表,帶(+)爲輔助表

select * from emp ,dept where emp.deptno=dept.deptno(+)   --左外連接
select * from emp ,dept where emp.deptno(+) = dept.deptno; --右外連接

在SQL99多個查詢表在from中寫第一個,後邊使用INNER JOIN 加上連接的第一個表名 ,然後ON後邊加上前邊兩個表的連接條件和篩選條件,還可以在where中再增加篩選條件。
在SQL99中的 外連接是如下:

select * from emp left  join dept on emp.deptno=dept.deptno  --左外連接
select * from emp right join dept on emp.deptno=dept.deptno   --右外連接

當做多表關聯,oracle中大概的寫法如下:

-- SQL92  易於書寫,不容易閱讀
select 內容 (別名,連接符,去除重讀,oracle函數,邏輯運算)
from 表名1,表名2,表名3...
where 條件(連接條件,普通篩選條件)
group by 分組字段
having 多行函數篩選
order by排序

-- SQL99 易於理解,不易書寫
select 內容 from 表名1,
inner join 表名2 on 連接條件
left  join 表名3 on 連接條件
where 普通篩選條件
group by 分組條件
having 多行函數篩選
order by 排序
on 和 where 區別

1,執行順序,on 在 where 之前,根據 on 中的 關聯條件和篩選條件對from 後面的表進行關聯產生中間表,然後在根據where子句過濾中間表數據得到最後結果。
2,返回結果,不管on上的條件是否爲真都會返回left或right表中的記錄,full則具有left和right的特性的並集。 而inner jion沒這個特殊性,則條件放在on中和where中,返回的結果集是相同的。
3,速度:因爲on是先把不符合條件的記錄過濾後才進行統計,它就可以減少中間運算要處理的數據,按理說應該速度是最快的。

where 和 having 區別

1,使用條件,having只能和group by一起使用,而where 並沒有這樣的限制。
2,執行順序,where 在 having 之前執行,過濾篩選結果,然後在執行聚合函數,再經過having過濾。
3,聚合函數,where 子句中沒法使用聚合函數,having 中可以使用聚合函數,並且聚合函數在having之前執行。

總結

ON、WHERE、HAVING的主要差別是其子句中限制條件起作用時機引起的,**ON是在生產臨時表之前根據條件篩選記錄,WHERE是從生產的臨時表中篩選數據,而HAVING是對臨時表中滿足條件的數據,進行計算分組之後,通過HAVING限制語句篩選分組,**返回結果是滿足HAVING子句限制的分組

三,表連接類型有內連接,外鏈接,那們在數據庫中是如何實現關聯?

在編寫SQL時,常用到left join 、right join 、join,但是這些連接類型在數據庫中是如何實現的呢?可以通過執行計劃來查看具體的連接方式。
在這裏插入圖片描述

1,驅動表與匹配表

JOIN 關鍵字用於將兩張表作連接,一次只能連接兩張表,JOIN 操作的各步驟一般是串行的(在讀取做連接的兩張表的數據時可以並行讀取),首先讀取的是驅動表,然後在根據條件去匹配表中匹配記錄。
驅動表(Driving Table):
表連接時首先存取的表,又稱外層表(Outer Table),如果驅動表返回較多的行數據,則對所有的後續操作執行效率有所影響,故一般選擇小表(應用Where限制條件後返回較少行數的表)作爲驅動表。
匹配表(Probed Table):
又稱爲內層表(Inner Table),從驅動表獲取一行具體數據後,再根據連接條件到內層表中查詢,所以內層表一般爲大表,這樣不需要查詢整個大表,只需根據條件查詢。

2,連接方式

表連接的幾種方式:
CARTESIAN PRODUCT(笛卡爾積)
SORT MERGE JOIN(排序-合併連接)
NESTED LOOPS(嵌套循環)
HASH JOIN(哈希連接)

2.1 CARTESIAN PRODUCT(笛卡爾積)
有如下查詢:

select a.*,d.*  from emp a,dept d

這種連接方式,比較簡單粗暴,將emp 中的每條記錄都與dept中的每條記錄進行關聯,假設 emp有n條記錄, dept有m條,則最終查詢的結果中有n x m 行記錄。這種連接方式一般都很少用到,

2.2 SORT MERGE JOIN(排序-合併連接)
以求第K大值爲例子
在這裏插入圖片描述
內部連接過程:
a) 生成 row source 1 需要的數據,按照連接操作關聯列(如示例中的t1.score)對這些數據進行排序
b) 生成 row source 2 需要的數據,按照與 a) 中對應的連接操作關聯列(t2.score)對數據進行排序
c) 兩邊已排序的行放在一起執行合併操作(對兩邊的數據集進行掃描並判斷是否連接)

延伸:
如果示例中的連接操作關聯列t1.score,t2.score之前就已經被排過序了的話,連接速度便可大大提高,因爲排序是很費時間和資源的操作,尤其對於有大量數據的表。
故可以考慮在 t1.score,t2.score上建立索引讓其能預先排好序。不過遺憾的是,由於返回的結果集中包括所有字段,所以通常的執行計劃中,即使連接列存在索引,也不會進入到執行計劃中,除非進行一些特定列處理(如僅僅只查詢有索引的列等)。
排序-合併連接的表無驅動順序,誰在前面都可以;
排序-合併連接適用的連接條件有: < <= = > >= ,不適用的連接條件有: <> like

2.3 NESTED LOOPS(嵌套循環)
內部連接過程:
a) 取出 row source 1 的 row 1(第一行數據),遍歷 row source 2 的所有行並檢查是否有匹配的,取出匹配的行放入結果集中
b) 取出 row source 1 的 row 2(第二行數據),遍歷 row source 2 的所有行並檢查是否有匹配的,取出匹配的行放入結果集中
c) ……
若 row source 1 (即驅動表)中返回了 N 行數據,則 row source 2 也相應的會被全表遍歷 N 次。
因爲 row source 1 的每一行都會去匹配 row source 2 的所有行,所以當 row source 1 返回的行數儘可能少並且能高效訪問 row source 2(如建立適當的索引)時,效率較高。

延伸:
嵌套循環的表有驅動順序,注意選擇合適的驅動表。
應儘可能使用限制條件(Where過濾條件)使驅動表(row source 1)返回的行數儘可能少,同時在匹配表(row source 2)的連接操作關聯列上建立唯一索引(UNIQUE INDEX)或是選擇性較好的非唯一索引,此時嵌套循環連接的執行效率會變得很高。若驅動表返回的行數較多,即使匹配表連接操作關聯列上存在索引,連接效率也不會很高。

2.4 HASH JOIN(哈希連接)
哈希連接只適用於等值連接(即連接條件爲 = )
哈希連接 其實 使用 哈希表查找算法
哈希查找也叫散列查找,整個散列查找過程大概分兩步:
(1)在存儲時通過散列函數計算記錄的散列地址,並按此散列地址存儲該記錄。
(2)當查找時,一樣通過散列函數計算記錄的散列地址,然後訪問散列地址的記錄。

內部連接過程簡述:
a) 取出 row source 1(驅動表,在HASH JOIN中又稱爲Build Table) 的數據集,然後將其構建成內存中的一個 Hash Table(Hash函數的Hash KEY就是連接操作關聯列),創建Hash位圖(bitmap)。
b) 取出 row source 2(匹配表)的數據集,對其中的每一條數據的連接操作關聯列使用相同的Hash函數並找到對應的 a) 裏的數據在 Hash Table 中的位置,在該位置上檢查能否找到匹配的數據。

補充:
關於連接方式詳細內容可以參考
https://www.cnblogs.com/Dreamer-1/p/6076440.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章