一個系列的讀書筆記,讀的書是有教無類和落落兩位老師編寫的《Oracle查詢優化改寫技巧與案例》。
用這個系列的讀書筆記來督促自己學習Oracle,同時,對於其中一些內容,希望大家看到以后,可以留下自己的想法。以此交流。
這篇隨筆主要記錄的是在Oracle查詢過程中對數字的使用具體分為九個部分
1.常用聚集函數
select deptno, min(sal) as 最小值, max(sal) as 最大值, sum(sal) as 工資合計, count(sal) as 計數, avg(sal) as 錯誤平均值, avg(coalesce(sal,0)) as 正確平均值 from emp group by deptno
正確平均值和錯誤平均值的意義:
聚集函數會忽略空值,對sum不會造成影響,但是對avg,connt會造成影響,所以根據需求決定是否需要把空值轉換為0。
注意:當表中沒有數據時,不加group by會返回一條數據,加了group by沒有數據返回。
驗證過程:創建表emp2
create table emp2 as select * from emp where 1 = 2
select count(*) from emp2 group by deptno
沒有group by :0
有group by:空值。
所以在實際使用過程中要注意group by 的位置。
2.生成累計和
案例:公司為了查看用人成本,需要對員工的工資進行累加,以便查看員工人數和工資支出之間對應的關系
以員工編碼(empno)進行排序,累加查看,查看部門編碼為 2的工資支出
select empno,empname,sal,sum(sal) over(order by empno) from emp where deptno = '2' order by empno
詳細看一下每一條數據的具體組成
select empno, empname, sal, sum(sal) over(order by empno), (select listagg(sal,'+') within group(order by empno) from emp b where b.deptno = '2' and b.empno <= a.empno) 計算公式 from emp a where deptno = '2' order by empno
效果如下:
3.計算累計差
案例:創建一個虛擬的流水賬,比如有30000的預算,然后有各種活動支出,每次支出完需要統計余額。
創建測試用表:
create table detail as select 1000 as 編號,'預交費用' as 項目,30000 as 金額 from dual;
在測試用表插入數據:
insert into detail select empno as 編號,'支出' || rownum as 項目,sal+1000 as 金額 from emp where deptno = '2'
detail表中為消費流水賬的內容。
方案:
(1)一般流水賬的編號都是按順序生成的,我們根據編號排序並生成序號。
select rownum as seq,a.* from (select 編號,項目,金額 from detail order by 編號 desc) a
(2)觀察查詢結果 seq = 1 的為收入,后面的為支出,可以用case when把后面的數據變為負數
with x as (select rownum as seq,a.* from (select 編號,項目,金額 from detail order by 編號 desc) a ) select 編號,項目,金額,(case when seq = 1 then 金額 else -金額 end) as 轉換后的值 from x;
(3)把轉換后的結果進行相加,可以得到差值
with x as (select rownum as seq,a.* from (select 編號,項目,金額 from detail order by 編號 desc) a ) select 編號,項目,金額,sum(case when seq = 1 then 金額 else -金額 end) over(order by seq) as 余額 from x;
4.更改累計和的值
創建測試視圖
create or replace view v(id,amt,trx) as select 1,100,'PR' FROM DUAL UNION ALL select 2,300,'PR' FROM DUAL UNION ALL select 3,150,'PY' FROM DUAL UNION ALL select 4,50,'PY' FROM DUAL UNION ALL select 5,200,'PY' FROM DUAL UNION ALL select 6,100,'PR' FROM DUAL UNION ALL select 7,300,'PY' FROM DUAL UNION ALL select 8,400,'PR' FROM DUAL ;
創建的視圖內容為一個存取款列表:
id是唯一列。
amt表示每次事務處理設計的金額。
trx列定義了事務處理的類型,取款是"PY",存款是"PR"。
(1)把取款值變為負數
select id, case when trx = 'PY' THEN '取款' else '存款' end 存取類型, amt 金額, case when trx = 'PY' THEN AMT ELSE -amt end 變更后的值 from v order by id
(2)把變更后的值進行相加
select id, case when trx = 'PY' then '取款' else '存款' end 存取類型, amt 金額, sum(case when trx = 'PY' THEN AMT ELSE -AMT END) over(order by id) 余額 from v order by id;
5.返回各部門工資排名前三位的員工
select deptno, empno, sal, row_number() over(partition by deptno order by sal desc) as row_number, rank() over(partition by deptno order by sal desc) as rank, dense_rank() over(partition by deptno order by sal desc) as dense_rank from emp where deptno in (2,3) order by 1,3 desc;
partition by:會把主查詢返回的子句分組進行分析。觀察查詢結果,對子句進行部門分組以后,部門為2的生成序列以后,部門為3的部門生成序列時,會重新進行分組。
當工資有重復項時,觀察row_number,rank,dense_rank的區別
row_number:仍然會生成序號1、2、3
rank:相同的工資會生成相同的序號,而且其后的序號與row_number相同,即1,1,3,3,5
dense_rank:相同的工資會生成相同的序號,其后的序號會遞增,即1,1,2,3,3,4
6.計算出現次數最多的值:
案例要求:查看部門中哪個工資等級的員工最多
(1)計算不同工資出現的次數
select sal,count(*) as 出現次數 from emp where deptno = 2 group by sal order by sal
(2)按次數排序生成序號
select sal,dense_rank() over(order by 出現次數 desc) as 次數排序 from (select sal,count(*) as 出現次數 from emp where deptno = 2 group by sal)x;
(3)根據序號過濾得到需要的結果
select sal from (select sal,dense_rank() over(order by 出現次數 desc)as 次數排序 from (select sal,count(*) as 出現次數 from emp where deptno = 2 group by sal)x)y where 次數排序 = 1
(4)利用partition by子句分別查詢各部門哪個工資等級的員工最多
select deptno,sal from (select deptno, sal,dense_rank() over(partition by deptno order by 出現次數 desc)as 次數排序 from (select sal,deptno,count(*) as 出現次數 from emp group by sal,deptno)x)y where 次數排序 = 1
7.返回最值所在行數據
方案1:
標量查詢:先取出最大值,再和最大值進行關聯,思路簡單,sql復雜
select a.empname as 工資最高的人,a.deptno,a.sal,a.max_sal from ( select max(sal) over(partition by deptno) as max_sal,empno,sal,empname,deptno from emp) a where sal = a.max_sal
方案2:
分析函數:在Oracle里有分析函數可以直接滿足這個需求,而且還可以方便的同時取最大值和最小值
select deptno, empno, max(empname) keep(dense_rank first order by sal) over(partition by deptno) as 工資最低的人, max(empname) keep(dense_rank last order by sal) over(partition by deptno) as 工資最高的人, empname, sal from emp order by 1,6 desc
first、last語句也可以放在group里與其他聚合函數一樣使用,這是要去掉后面的over(partition by xxx)
select deptno, min(sal) as min_sal, max(empname) keep(dense_rank first order by sal) as 工資最低的人, max(sal) as max_sal, max(empname) keep(dense_rank last order by sal) as 工資最高的人 from emp group by deptno
在第一個分析函數的語句中,不論是first,還是last,都用聚合函數MAX,分析一下MAX的作用
select deptno, empno, max(sal) over(partition by deptno) as 最高工資, empname, sal from emp where deptno = 3 order by 1,5 desc
根據表中的數據,工資最高的有兩個人,加上first和last語句
select deptno, empno, empname, sal, to_char(wmsys.wm_concat(empname) keep(dense_rank last order by sal) over(partition by deptno)) as 工資最高的人, min(empname) keep(dense_rank last order by sal) over(partition by deptno) as 工資最高的人min, max(empname) keep(dense_rank last order by sal) over(partition by deptno) as 工資最高的人max from emp where deptno = 3 order by 1,4 desc
編碼為3的部門工資最高的有兩個,通過查詢結果,可以看到keep()得到的結果包含兩個人名字,所以通過min和max可以取到不同的值。
8.first_value
用first_value 和 last_value 來替換 first 和 last
select deptno, empno, first_value(empname) over(partition by deptno order by sal desc) as 工資最高的人, empname, sal from emp where deptno = 3 order by 1,5 desc
上面sql的結果沒有問題
9.求總和的百分比
需求:計算各部門的工資合計,及該合計工資占總公司的比例
(1)分組匯總
select deptno,sum(sal) 工資合計 from emp group by deptno
(2)通過分析函數獲取總合計
select deptno,工資合計,sum(工資合計) over() as 總合計 from (select deptno,sum(sal) 工資合計 from emp group by deptno) x;
(3)通過前兩步的結果計算
select deptno, 工資合計, round((工資合計/總合計)*100,2) as 工資比例 from ( select deptno,工資合計,sum(工資合計) over() as 總合計 from (select deptno,sum(sal) 工資合計 from emp group by deptno) x) y;
也可以用專門的比例函數“ratio_to_report”
select deptno, round(ratio_to_report(工資合計)over() * 100,2) as 工資比例 from(select deptno,sum(sal) 工資合計 from emp group by deptno) order by 1
同其他分析函數一樣,可以使用partition by 分組計算,如查詢各員工占本部門的工資比例:
select deptno, empname, sal, round(ratio_to_report(sal) over(partition by deptno)*100,2) as 工資比例 from emp order by 1,2