SQL 计算中位数

SQL 计算中位数,你想计算某个数值列的中位数(即按顺序排列的一组数据中居于中间位置的数)。例如,你希望知道 DEPTNO 等于 20 的部门里员工工资的中位数。就如下示例而言,中位数为 2975。

SQL 计算中位数 问题描述

你想计算某个数值列的中位数(即按顺序排列的一组数据中居于中间位置的数)。例如,你希望知道 DEPTNO 等于 20 的部门里员工工资的中位数。就如下示例而言,中位数为 2975。

select sal
  from emp
 where deptno = 20
 order by sal
 
       SAL
----------
       800
      1100
      2975
      3000
      3000

SQL 计算中位数 解决方案

除了 Oracle 的解决方案(Oracle 有可以计算中位数的函数),其他解决方案都基于 David Rozenshtein、Anatoly Abramovich 和 Eugene Birger 在 Optimizing Transact-SQL 一书中记载的方法。窗口函数的出现使得我们有了比传统的自连接查询更为高效的做法。
DB2
使用窗口函数 COUNT(*) OVERROW_NUMBER 查找中位数。

 1  select avg(sal)
 2    from (
 3  select sal,
 4         count(*) over() total,
 5         cast(count(*) over() as decimal)/2 mid,
 6         ceil(cast(count(*) over() as decimal)/2) next,
 7         row_number() over (order by sal) rn
 8    from emp
 9   where deptno = 20
10         ) x
11   where ( mod(total,2) = 0
12           and rn in ( mid, mid+1 )
13         )
14      or ( mod(total,2) = 1
15           and rn = next
16         )

MySQLPostgreSQL
使用自连接查询查找中位数。

 1  select avg(sal)
 2    from (
 3  select e.sal
 4    from emp e, emp d
 5   where e.deptno = d.deptno
 6     and e.deptno = 20
 7   group by e.sal
 8  having sum(case when e.sal = d.sal then 1 else 0 end)
 9                            >= abs(sum(sign(e.sal - d.sal)))
10         )

Oracle
使用函数 MEDIAN(Oracle Database 10g)或者 PERCENTILE_CONT(Oracle 9i)。

1 select median(sal)
2   from emp
3  where deptno=20
 
1 select percentile_cont(0.5)
2         within group(order by sal)
3   from emp
4  where deptno=20

DB2 解决方案也适用于 Oracle 8i。对于 Oracle 8i **之前的版本,就要采用 MySQLPostgreSQL 的解决方案。
**SQL Server

使用窗口函数 COUNT(*) OVERROW_NUMBER 查找中位数。

 1  select avg(sal)
 2    from (
 3  select sal,
 4         count(*) over() total,
 5         cast(count(*) over() as decimal)/2 mid,
 6         ceiling(cast(count(*)over() as decimal)/2) next,
 7         row_number() over(order by sal) rn
 8    from emp
 9   where deptno = 20
10         ) x
11   where ( total%2 = 0
12           and rn in ( mid, mid+1 )
13         )
14      or ( total%2 = 1
15           and rn = next
16         )

SQL 计算中位数 扩展知识

DB2 和 SQL Server
DB2 和 SQL Server 的解决方案之间的唯一不同之处在于一个语法细节:SQL Server 的取模运算符是 %,DB2 则使用 MOD 函数。除此之外,二者并无二致。内嵌视图 X 返回 TOTALMIDNEXT 三种不同的总数以及 ROW_NUMBER 函数生成的 RN。这些额外生成的列有助于查找中位数。仔细观察内嵌视图 X 返回的结果集,并看看这些列表示什么。

select sal,
       count(*) over() total,
       cast(count(*)over() as decimal)/2 mid,
       ceil(cast(count(*)over() as decimal)/2) next,
       row_number()over(order by sal) rn
  from emp
 where deptno = 20
 
 SAL TOTAL  MID NEXT   RN
---- ----- ---- ---- ----
 800     5  2.5    3    1
1100     5  2.5    3    2
2975     5  2.5    3    3
3000     5  2.5    3    4
3000     5  2.5    3    5

为了找到中位数,SAL 列的值必须按照从小到大的顺序排列。由于 DEPTNO 等于 20 的员工的个数为奇数,因此中位数就是 RNNEXT 相等的那一行的 SAL 值(NEXT 表示大于员工总数除以 2 的商的最小整数)。
如果内嵌视图 X 返回的结果集的行数为奇数,则 WHERE 子句的前半部分(第 11 ~ 13 行)不会命中。如果我们确信内嵌视图 X 返回的结果集的行数始终是奇数,不妨简化为如下的形式。

select avg(sal)
  from (
select sal,
       count(*)over() total,
       ceil(cast(count(*)over() as decimal)/2) next,
       row_number()over(order by sal) rn
  from emp
 where deptno = 20
       ) x
 where rn = next

不幸的是,如果子查询返回的结果集的行数为偶数,上述简化的做法不再适用。该解决方案使用 MID 列来处理偶数行的情况。如果 DEPTNO 等于 30,则内嵌视图 X 的返回结果会包含 6 个员工。

select sal,
       count(*)over() total,
       cast(count(*)over() as decimal)/2 mid,
       ceil(cast(count(*)over() as decimal)/2) next,
       row_number()over(order by sal) rn
  from emp
 where deptno = 30
 
 SAL TOTAL  MID NEXT   RN
---- ----- ---- ---- ----
 950     6    3    3    1
1250     6    3    3    2
1250     6    3    3    3
1500     6    3    3    4
1600     6    3    3    5
2850     6    3    3    6

由于一共有偶数行数据,因此中位数就变成了其中两行数据的平均值,相关的两行是 RN 等于 MID 的那一行以及 RN 等于 MID+1 的那一行。
MySQL 和 PostgreSQL
首先通过自连接 EMP 表来计算中位数,自连接查询会返回所有工资值的笛卡儿积(但是针对 E.SALGROUP BY 之后会去掉重复值)。HAVING 子句使用 SUM 函数来计算 E.SALD.SAL 相等的次数。如果它们相等的次数不小于 E.SAL 大于 D.SAL 的次数,则对应的行就是中位数。不妨把 SUM 函数添加到 SELECT 列表中,以观察查询结果,并验证这一点。

select e.sal,
       sum(case when e.sal=d.sal
                then 1 else 0 end) as cnt1,
       abs(sum(sign(e.sal - d.sal))) as cnt2
  from emp e, emp d
 where e.deptno = d.deptno
   and e.deptno = 20
 group by e.sal
 
 SAL CNT1 CNT2
---- ---- ----
 800    1    4
1100    1    2
2975    1    0
3000    4    6

Oracle
对于 Oracle Database 10g 或者 Oracle 9i,可以使用 Oracle 中的函数计算中位数。对于 Oracle 8i,不妨使用 DB2 解决方案。对于更早的 Oracle 版本,只能采用 PostgreSQL 解决方案。MEDIAN 函数明显是用于计算中位数的,而 PERCENTILE_CONT 函数的用途看起来就不那么直观。传递给 PERCENTILE_CONT 函数的参数 0.5 其实是一个百分位数值。WITHIN GROUP (ORDER BY SAL) 子句会生成顺序排列的行数据,以便于 PERCENTILE_CONT 函数搜索。(注意,中位数就是在一组顺序排列的数值里居于中间位置的那个数。)最后的返回值是顺序排列的行数据里落入指定百分位的数值(本例中的百分位数值是 0.5,居于边界值 0 和 1 的中间)。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程

SQL 实例