中位數的數學定義
中位數的定義如下圖所示:
在oracle
數據查詢中,當N為偶數
時,一般不取平均值
,因為這有可能創建出新的樣例使得查詢語句為空,所以通常為偶數時,一般返回最中間的一組數據。
oracle中位數查詢
1.求解思路
目前常見的求解主要包括以下兩種思路:
1)從數值角度出發;
先按照目標列進行排序,然后按照總行數的奇偶性,利用類似上圖的數學方法進行方法進行篩選。
2)從數值索引(所在位置)出發
這種方法從中位數可能出現的位置,進行巧妙構造,以選取目標位置。
類似於python
中根據目標索引
對list
進行切片
。其更符合中位數的定義,即有一半的數大於中位數,有一半數小於中位數。
實際應用中出於不同的考慮,對於內置函數median
選擇性使用
2.應用案例
為了方便說明,采用leetcode
上的案例:編寫SQL查詢來查找每個公司的薪水中位數。
Id | Company | Salary |
---|---|---|
1 | A | 2341 |
2 | A | 341 |
3 | A | 15 |
4 | A | 15314 |
5 | A | 451 |
6 | A | 513 |
7 | B | 15 |
8 | B | 13 |
9 | B | 1154 |
10 | B | 1345 |
11 | B | 1221 |
12 | B | 234 |
13 | C | 2345 |
14 | C | 2645 |
15 | C | 2645 |
16 | C | 2652 |
17 | C | 65 |
- 表創建語句:
create table employee(
id number(3) primary key,
company varchar2(2),
salary number(8)
);
insert into employee values(1,'A',2341);
insert into employee values(2,'A',341);
insert into employee values(3,'A',15);
insert into employee values(4,'A',15314);
insert into employee values(5,'A',451);
insert into employee values(6,'A',513);
insert into employee values(7,'B',15);
insert into employee values(8,'B',13);
insert into employee values(9,'B',1154);
insert into employee values(10,'B',1354);
insert into employee values(11,'B',1221);
insert into employee values(12,'B',234);
insert into employee values(13,'C',2345);
insert into employee values(14,'C',2645);
insert into employee values(15,'C',2645);
insert into employee values(16,'C',2652);
insert into employee values(17,'C',65);
(一)使用median
函數的情況
select id,company,salary
from (
select tmp.* ,median(r_num) over(partition by company) med
from (
select e.*, row_number() over(partition by company order by salary) r_num
from employee e) tmp)
where abs(r_num-med)<=0.5;
程序解釋:利用median
求出的中位數是數學上的定義
,不是我們所需要的.所以利用median
進行改造.思路:獲取每一份組的行號row_number(對應的新列記為r_num)和median(對應的新列記為med),當abs(r_num-med)<=0.5時,說明取到了中位數
;
統計結果如下:
(二)不使用median
函數的情況
(1)----->>>對應的第一種方法
這種方法需要區分不同分組數據量的奇偶性,在使用where
的時候需要考慮單值和多值混合查詢。
具體查詢語句:
select id,company,salary
from (
select e.*, count(*) over(partition by company) x,row_number() over(partition by company order by salary) r_num
from employee e) tmp
where r_num in (ceil(x/2),x/2+1);
最終的查詢結果:
值得注意的是where r_num in (ceil(x/2),x/2+1)
的設定。當x是奇數
時,(ceil(x/2),x/2+1)
中只有一個有效(即ceil(x/2)=(x+1)/2
);當x是偶數時
,(ceil(x/2),x/2+1)
=(x/2,x/2+1)
就是最中間的一組數據。
這類方法的其它查詢案例,大多需要通過group by+連接
構造,相對於上面的比較復雜,本文暫且不考慮其它方法.
(2)----->>>對應的第一種方法
select e1.id,e1.company,e1.salary
from employee e1,employee e2
where e1.company=e2.company(+)
group by e1.company,e1.salary,e1.id
having sum(decode(e1.salary-e2.salary,0,1,0))
>=abs(sum(sign(e1.salary-e2.salary)))
order by e1.id;
程序分解:以A公司為例,共有6條數據。
上述程序中核心思路是采用自連接+having條件
。having
條件構造的特別巧妙。所以將對其進一步分解:如下圖所示。
中間的命令行截圖為A公司數據的自連接結果(按照salary)排序。其中“+”
表示大於當前對象的樣例個數(后面的具體數字);“0”
表示相等;“-”
表示小於。sum(sign())
表示having
中的部分結果。x=sign(a)
為符號函數,a>0,x=1;a=0,x=0,a<0,x=-1
.
其中having
的統計結果如下圖所示,每一組中相等的情況中只有一種圖表中的A列
(A公司正好沒有重復數據),abs()
的統計結果為(圖表中的B列
),具體計算方法見上圖(關聯結果)
(2)----->>>對應的第二種方法
思路仍然是從中位數的定義出發,只不過引用的核心函數是row_number,和count(*)
。其中row_number
的使用方法,請查看oracle學習筆記(六):oracle中排序函數及其應用_數據庫_qq_40584718的博客-CSDN博客 。
具體程序(來源於leetcode)如下:
/* Write your PL/SQL query statement below */
select
id, company, salary
from
(select
id, company, salary,
row_number() over (partition by company order by salary) as rn, -- 各薪水記錄在其公司內的順序編號
count(1) over (partition by company) as cnt -- 各公司的薪水記錄數
from employee
)
where abs(rn - (cnt+1)/2) < 1 -- 順序編號在公司薪水記錄數中間的,即為中位數
核心程序解讀:
(1) row_number() over()
按照公司分組,並按照薪水排序,將該結果保存為新列rn
;
(2) count(1) over()
應該和count(*) over()
效果相同,是用來統計不同公司的樣例條數;
(3) where
調價,這個程序的靈魂
。rn
可以理解為一個列表,where
的運行過程可以理解為下圖:
where abs(rn - (cnt+1)/2) < 1 -- 順序編號在公司薪水記錄數中間的,即為中位數
這里的<1
說明rn
中有(cnt+1)/2
特別靠近的行號(或者理解為索引或位置)存在。舉例說明:
假如rn=[1,2,...8]
,則(cnt+1)/2=4.5
,中對數對應的索引為4和5
;
若rn=[1,2,...,7]
,則(cnt+1)/2=4
,那么這個中位數對應的索引只能是4
.