oracle:中位数查询整理
中位数的数学定义
中位数的定义如下图所示:
在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
.