Oracle的高級查詢-分組、連接、子查詢、分頁

Oracle的高級查詢

概述:Oracle作爲一個老牌數據庫,風風雨雨經歷了二三十年。我們平時工作的時候,或多或少也會接觸到Oracle數據庫的使用。在數據庫開發中,簡單的單表操作,對大部分剛入行的新人來說,不會有太大的問題。
但是隨着我們所開發業務系統的功能多樣化,爲了實現複雜的需求,我們就會使用的高級查詢。Oracle高級查詢中,比較難理解的是分組查詢,連接查詢以及子查詢,以及基於子查詢的分頁查詢。因爲這些高級查詢的結果會和原有的表結構差異很大,不太好理解。
下面我將結合Oracle中內置的Scott賬號,對Oracle中的高級查詢在進行一些回顧。

一.scott系統內置賬號

Oracle數據庫成就了拉里·埃裏森,從埃裏森32歲一無所有,到20年後的硅谷首富,鋼鐵俠原型,成就了一段傳奇。
不過和拉里·埃裏森同時期的技術人員Scott就沒有像埃裏森那樣閃亮,Scott在Oracle中除了一個賬號,就什麼都沒有留下。不過我們要練習Oracle數據庫,使用Scott賬號還是很不錯的,通過Scott賬號內置的幾張表,我們就能實現出一些比較高級的查詢。

1.開啓scott賬號

  • 開啓scott賬號的方法,用system賬號登錄,因爲system具有dba權限,通過解鎖賬號的sql命令
--解鎖Scott賬號
alter user scott account unlock;

賬號解鎖後,再給scott賬號重置密碼,這裏密碼我們用tiger

--重置scott密碼,設置爲tiger
alter user scott identified by tiger;

此時scott具有默認的兩個角色分別是connect角色和resource角色,就可以用於對錶進行基本操作。
系統切換到scott賬號下
在這裏插入圖片描述

2.scott賬號下的員工表和部門表

Scott賬號下有員工表,員工表爲:emp,要查看員工表的數據,可以採用查詢語句

--查詢所有員工
select * from emp;

在這裏插入圖片描述
所用員工中,SCOTT的的工號是7788,他的崗位(Job)爲分析師(Analyst),領導的工號是7566,入職日期是1987年4月19日,當時它的薪資是3000美元,沒有獎金,在20號部門工作。
該賬號下還有一個部門表,要查看部門表下面的數據

--查詢所有部門
select * from dept;

在這裏插入圖片描述
通過部門表,可以得知,scott所在的20號部門是研發部,位於達拉斯(美國得克薩斯州東北部城市),接下來我們將用這2張表進行Oracle的高級查詢

二.聚合查詢

我們要統計公司中有多少員工,剛纔通過select * from emp語句,查詢出得結果我們數了一下有14條記錄,每行記錄前面也有序號,數起來也比較方便。
難道以後我們都是通過數一數的方式得到員工總數的麼?其實這個方法是可行的,就是有點弱智,不符合我們程序人員的身份。

1.聚合查詢的應用場景

對於統計總數,平均數,最大值等操作,Oracle中爲我們提供了專門的聚合函數。使用聚合函數查詢就是聚合查詢,聚合函數有個非常明顯的特點,就是被查詢的表,有多行記錄,而查詢出得結果,只有一行數據,簡而言之就是將多行數據變爲了一行數據,有的地方也稱爲多行函數。

--查詢員工總數
select count(1) from emp;

在這裏插入圖片描述
原始數據有14行,通過聚合查詢,最終的表結構是一行一列的結果。count(1)同count(deptno)一樣

2.聚合函數的空值處理

需要注意的地方,對於聚合函數中的count對空值得處理
在這裏插入圖片描述

聚合函數count只對不爲null的列進行統計,這是Oracle數據庫在使用count的一個特性,對單字段統計的時候,不同字段的統計會有不一樣結果,因爲表中存在爲空的值,這點尤其要注意

3.聚合查詢總結:

  • 常用的聚合函數有五種,分別是count,max,min,avg,sum。其中count可以作用於任何字段,而後面的四種只能作用於數字型的字段(NUMBER);
  • 在聚合函數中,一般使用count(1)比count(*)要好一些,count(1)的意思是隻對第一列進行統計總數,第一列也通常爲表格的主鍵,效率會高一些;
  • count作用的列,不包含空值的數據,如select count(comm) from emp統計得到的結果是4,因爲只有4名員工的獎金的不爲空值;
  • count可以和distinct組合使用,如統計該部門一共有多少種崗位,統計崗位種類數目的時候,要去除重複的列,得到的sql查詢語句爲:select count(distinct job) from emp 執行的結果爲 5;這五個崗位分別爲CLERK,SALESMAN,PRESIDENT,MANAGER,ANALYST,感興趣的朋友可以去原始表中找一找。

三.分組查詢

分組查詢是數據庫查詢中一個難以理解的地方,因爲分組查詢改變了表的原始結構。先來說說什麼是分組查詢。

1.分組查詢的機制

分組查詢是根據某種規則,將數據分組,每組數據再通過聚合函數,轉換成爲一行數據,最後再將每行數據聯合在一起,得到最終的數據呈現給用戶。
分組查詢常常用於統計。如查詢每個部門的最高薪資:

--統計每個部門的最高薪資
select deptno,max(sal) from emp group by deptno order by deptno asc;

在這裏插入圖片描述
這裏使用order by排序是爲了方便我們查看原始數據,便於學習理解,實際使用中,不使用order by不會對分組的查詢的最終結果產生影響。

2.分組查詢的語法


select {column}, --group by後面的列select後面的列一致
聚合(聚合列) --其他字段採用聚合確保行數匹配
from {table} --需要被統計分組數據的原始表
where {before_condition}  --分組前的數據進行篩選,使用where
group by {column} --被用於分組的那一列
having {after_condition} --對分組後的數據進行篩選
  • 說明一哈
    {column}:表示我們要分組的列,如我們依據部門進行分組,column就是deptno
    聚合(聚合列):這裏就要用到聚合函數,壓縮行數,不然行數不匹配就報錯了
    {table}表示原始數據所在的表格,裏面可以是一個返回多行多列的子查詢,可以使是視圖
    {before_condition} 裏面的篩選條件,對分組前的數據篩選
    group by {column}:裏面的column和select後面的column要一樣
    {after_condition}:對分組查詢後的數據進行篩選

3.分組查詢子句執行順序

我們在開發中,使用的分組查詢的語法順序是:
select -> 聚合 -> from ->where ->group by ->having
而實際上,在Oracle中,對SQL中的每個子句,執行順序和編寫順序是不同的,採用下列方式執行
from ->where ->group by ->聚合-> having ->select
正因爲執行順序的不同,在條件判斷中,不能使用聚合函數中所用到的別名,這點在別名的使用的時候尤其要注意。

4.分組查詢的應用場景

分組查詢除了用於一般的統計,還有很多方便靈活的用法。
舉一個開發中的例子:我們設計權限系統中的用戶名必須唯一,但是因爲起初設計的原因,並沒有加入數據庫的唯一性約束,Java邏輯中也沒有做唯一性的驗證判斷,或者繞開了Java的唯一性校驗規則,這個是很容易發生的,我們數據不小心進入到了系統,並沒有被我們發現。程序大部分用戶是沒有問題的,可是一旦用了同名的賬號登錄,程序就會發生異常,系統容錯沒有做好的話,功能就失效了。
我們要找到這些重複的髒數據,就比較頭疼了,因爲這些壞數據時刻干擾着我們的邏輯,我們要清理掉壞數據的前提是能夠找到這些重複的數據,這個時候,我們的分組查詢就大顯神威了。
這裏用emp表的job字段模擬重複數據,我們的查詢爲

--得到job中不唯一的崗位,並統計在數據中出現的次數
select job,count(job) from emp group by job having count(job) >1;

在這裏插入圖片描述
那如果是找出重複的用戶名,就更簡單了
select username,count(username) from user group by username having count(username) >1;
通過這種分組查詢,找出了用戶名,再通過其他操作,對這些用戶名重複的數據進行處理就簡單多了。

5.分組查詢總結

只要理解了分組查詢的作用機制,使用規則,結合使用場景,分組查詢的優勢還是很明顯的,使用的時候注意以下幾點就沒問題

  • 1.被分組的那一個列要放在group by後面,同時要作爲查詢列,其他列採用聚合函數;
  • 2.where後面跟着的查詢條件爲分組前的數據篩選,having分組後的數據篩選;
  • 3.條件中,不能使用別名,因爲條件子句是最先執行的;

四.連接查詢

在學習連接查詢的時候,我們要搞清楚連接查詢的由來。根據數據庫規範化設計理論,我們爲了減少數據冗餘,完成數據更新,修改的一致性,要對數據庫表進行拆分。也就是出現了數據庫一對多,多對多的關係。
但是我們在開發應用層面上,有需要用到這些數據,如我們想知道Scott的部門名稱,就需要先去員工表中找到scott的部門號,再到部門表中根據部門號,找到部門名稱。
我們最終得到的數據要通過員工表和部門表的拼接,這個時候我們要用到的連接查詢。

在這裏插入圖片描述
連接查詢中: 交叉連接外連接的超集,外連接內連接的超集。
如我們要得到部門以及部門員工信息,部門表中有4條記錄,員工表中有14條記錄,採用交叉連接,將會得到14*4=56條記錄,這種交叉連接的方式,也叫笛卡爾集
外連接就是要在這56條記錄中,篩選出符合條件的數據。如查詢部門以及部門下的員工信息

--查詢部門以及部門下的員工信息
select * from dept left join emp on dept.deptno = emp.deptno

在這裏插入圖片描述
外連接分爲2中,分別爲左外連接,和右外連接,他們的作用原理一樣的,只是方向不一樣而已。
如上面的寫法可以改寫爲:left join/right join

--查詢部門以及部門下的員工信息
--左連接的方式
select * from dept left join emp on dept.deptno = emp.deptno;
--右連接的方式
select * from emp right join dept on dept.deptno = emp.deptno;

最終得到的數據,我們觀察,是15條記錄,可是員工總數有14條,因爲40號部門底下沒有員工,我們的需求是所有部門信息,所以最終結果要保留40號部門。是滿足我們的需求的。
如果我們要在外連接的基礎上,不保留40號部門,我們就要用到內連接內連接外連接數據結果集的子集。
內連接的寫法關鍵字 inner join

select * from dept inner join emp on dept.deptno = emp.deptno;

此時,將自動過濾 結果集中deptno爲空值的記錄。
內連接還有一種簡便的寫法,即爲逗號(,)寫法

select * from dept,emp where dept.deptno = emp.deptno;

這裏再做一個需求理解上的區分:
查詢所有員工以及所在部門信息查詢所有部門以及部門下的員工信息,最終查詢的結果是一樣的麼?

我們要認真審題:是不是所有的員工都有部門號?是不是所有的部門下面都有員工?

  • 規律一:如果所有的員工都有部門號,所有的部門下都有員工
    內連接,外連接,左連接以及右連接,最終查詢的結果集的大小是一樣的,只是結果集的字段順序有些差異而已,我們通過對錶起別名,可以最終控制字段顯示的順序,達到我們的效果。這個場景下,我們爲了省事,不適用join關鍵字,採用逗號寫法是完全沒有問題的,也就是說,我們使用的內連接查詢。
  • 規律二:如果有的員工沒有部門號,如剛到公司實習生還沒有定部門,但也是公司的員工啊;或者有的部門底下沒有員工,如剛成立的新部門,或者部門底下的員工都跑路了。這個時候尤其要注意我們的需求描述的差異,如果我們的需求描述中出現“所有員工”、“全部部門”、“每個”等字眼,要認真分析需求,或者隱含着查詢所有等這層意思,就一定不能用內連接,此時的內連接,會自動過濾掉哪些連接字段爲空值的哪些數據,最終數據時外連接查詢的子集

五.子查詢

子查詢是我認爲Oracle中還比較好理解的一種查詢用法

1.什麼是子查詢

子查詢就是把一個查詢的結果作爲另外一個查詢的條件進行查詢。
平時用子查詢的地方還挺多的,在通俗一點,我認爲子查詢就是括號查詢,因爲子查詢的語句中,都會有一個成對的小括號(),在小括號裏面做內層子查詢,括號外面的是外層查詢,子查詢也成爲了SubQuery,我們如果用到了查詢分析器Explain,查詢分析器Explain會告訴我們是不是用到了SubQuery

2.子查詢返回一行一列

這部分還是比較好理解的,子查詢返回的是一行一列,將返回值作爲條件進行判斷,如我們的=,>,<
舉個例子:我們查詢比scott薪資高的員工信息
第一步:我們要得到scott的薪資 select sal from emp where ename = ‘SCOTT’,
第二步:就可以將第一步的結果作爲條件了 select * from emp where sal > (step1)
在這裏插入圖片描述
通過上面的查詢,我們得到了一個重要的信息:SCOTT當年在公司的待遇還是蠻不錯的,畢竟比他薪資高的人也只有KING了

3.子查詢返回多行一列

  • 如果內層子查詢返回的結果是多行一列的話,就要用到in,此時的內層子查詢返回的就是一個結果集合,用in進行匹配的話,就是集合運算

4.子查詢返回一張表

通過子查詢返回一張表的特性,我們可以快速的創建、複製一張表操作
create table temp_table as {subquery};
在這裏插入圖片描述
快速的備份、複製一張表,非常的方便
通過子查詢返回的表,通過起別名,依然可以進行分組查詢,連接查詢,就如同真實存在的一張表一樣進行操作,都是沒問題的。

5.巧妙的實現排名的功能

--得到薪資排名
select e1.*,(select count(1)+1 from emp e2 where e2.sal > e1.sal) rk 
from emp e1 order by rk;

在這裏插入圖片描述
上述查詢過程進行分析,匹配到比KING薪資高的人,有0人,所以排名第1。
比SCOTT薪資高的人只有1人,所以排名第2,同樣比FORD薪資高的人,也只有KING一人,也是排名第2。
到JONES的時候,比JONES薪資高的人有3人,排名第4。按照這個規律,查詢出比某個員工高的人的人數,在數目的基礎上+1,就得到了此員工的排名,所以採用了count(1)+1的寫法。
所以利用子查詢,十分巧妙的實現了薪資的排名功能。

六.分頁查詢

1.ROWNUM的特性

Oracle的分頁和MySQL數據庫不一樣,不支持limit關鍵字,但是Oracle有自身的特性,支持ROWNUM字段。Oracle在使用ROWNUM字段的時候,查詢出結果集中,ROWNUM始終是從1開始的,這就需要我們在對ROWNUM進行條件篩選的時候,不能大於某個正整數。如果採用了ROWNUM大於某個正整數的寫法,就會查詢不到任何數據。

2.利用ROWNUM和子查詢進行分頁

在ORACLE進行分頁的時候,數據的開始行號和結束行號在分頁之前已經計算完畢,如對員工薪資從高到低進行排序後,每頁顯示5條記錄,顯示第2頁的記錄。開始行就是6,結束行就是10.

--對員工薪資從高到低進行排序後,每頁顯示5條記錄,顯示第2頁的記錄
select * from (
select rownum rn,t.* from(
select * from emp order by sal desc
)t where rownum <= 10
) where rn => 6

在這裏插入圖片描述
分頁查詢還有一種寫法,使用between關鍵字寫法如下

select * from (
select rownum rn,t.* from(
select * from emp order by sal desc
)t 
) where rn between 6 and 10

在數據量小的時候,兩種查詢差別不是很大。但是對於大數據量的分頁,第二種的查詢效率沒有第一種查詢效率高,因爲Oracle會默認對子查詢的條件進行優化,在CBO 優化模式下,將外層的查詢條件推到內層查詢中,以提高內層查詢的執行效率。
對於第一種方式的查詢語句,第二層的查詢條件WHERE ROWNUM <= 10就可以被Oracle推入到內層查詢中,這樣Oracle查詢的結果一旦超過了ROWNUM限制條件,就終止查詢將結果返回了。
而第第二種查詢語句,由於查詢條件BETWEEN 6 AND 10是存在於查詢的第三層,而Oracle無法將第三層的查詢條件推到最內層(即使推到最內層也沒有意義,因爲最內層查詢不知道RN代表什麼)。
因此,對於第二個查詢語句,Oracle最內層返回給中間層的是所有滿足條件的數據,而中間層返回給最外層的也是所有數據。數據的過濾在最外層完成,顯然這個效率要比第一個查詢低得多。

總結一下分頁的用法:

  • 1.分頁查詢的時候,有排序需求的,需要在最內層子查詢進行排序;
  • 2.rownum不能大於某個正整數,只能小於某個正整數;
  • 3.外層查詢要用到內層查詢的rownum列,要給rownum起別名;
  • 4.數據量較大的情況下,採用內層子查詢加條件限制,能提高分頁查詢效率。

注意點:在Java代碼做分頁的時候,特別是使用MyBatis分頁插件的時候需要注意:
依據Oracle分頁的原理,我們在業務sql後面不能寫分號;如果寫了分號,就要對分號進行處理,如果分頁插件沒有對分號進行處理,在對原有SQL語句進行解析的時候,就前後拼接Oracle分頁語法規則,此時的分號就會執行不成功。不過,如果分頁插件對原始SQL語句做了分號截斷處理,就沒有這個問題了。

---分頁插件作用機理
select * from (select rownum rn,t.* from(  ---分頁插件幫我們在原來的sql語句前拼接

select * from emp order by sal desc  --我們自己編寫的業務代碼,如果分頁插件沒有分號檢測截取操作,就我們就不能再此後面加上分號

)t where rownum <= 10) where rn => 6  --分頁插件幫我們在原來的sql語句後拼接

總結

Oracle的高級查詢對比基本的單表查詢看起來會顯得複雜一些,但正是因爲如此,Oracle的查詢語句體現出自身非常靈活的地方。
我們在日常的數據庫開發中,能夠熟練的掌握高級查詢技巧,就能夠提高我們的開發效率,會有更多的時間陪伴我們的家人。
最後感謝我的女朋友李強妮同學對我工作的支持,鼓勵我堅持技術博客的寫作。


作者:熊鑫
郵箱:[email protected]
轉載請告知

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