SQL笔面试题:如何求取中位数?

数据管道

共 3838字,需浏览 8分钟

 ·

2021-07-27 18:28

大家好,我是宝器。

前面写了篇:最近的面试,里面提到了我问了中位数的求取正好看到胖里做了汇总,分享下给大家

先来看看中位数的概念

中位数(Median)又称中值,统计学中的专有名词,是按顺序排列的一组数据中居于中间位置的数,代表一个样本、种群或概率分布中的一个数值,其可将数值集合划分为相等的上下两部分。对于有限的数集,可以通过把所有观察值高低排序后找出正中间的一个作为中位数。如果观察值有偶数个,通常取最中间的两个数值的平均数作为中位数。

百度百科

说到中位数,大家应该都不陌生。中位数是九年义务教育中数学里的一个重要概念,想当初刚学习的时候会经常对平均数、中位数、众数进行比较,比较它们的优缺点是什么,什么情况下应该用哪一种数。

不知道大家日常数据分析工作中是否会用到中位数的求取,如果是通过Excel、Python等进行数据分析、可视化展示,那这两款软件都有其对应的求中位数的函数,分别是median()和numpy库中的median()。如果写SQL的时候用到,可能就没有那么现成的函数了。(oracle中有求取中位数的函数)

除了日常工作,数据分析的笔面试中也经常会出现中位数求取的考察。今天就总结三种用SQL求取中位数的方法。

方法1:充分利用窗口函数

思路介绍:

根据中位数的概念,想要求取中位数,需要对一组数据进行排序,找出居于中间位置的数,如果有奇数个数,那最中间的一个为中位数,如果有偶数个数,那中间两个数的平均数为中位数。因此我们需要实现的是排序,取中间值或中间两值的平均值,既然涉及到中间这种位置定位,那自然少不了编号与总体个数的比较。

因此可以概括为排序、编号、找位置、取值。那就需要考虑到排序函数row_number()、计数函数cout()、求均值函数avg()。

示例:

有一组数:1,3,55,8,34,66,42,88,SQL找出中位数。

按照先前的思路,使用相关函数实现:

select avg(num)from (    select num         ,row_number() over(order by num) as rn         ,count(*) over() as n    from tmp)as t where rn in (floor(n/2)+1,if(mod(n,2) = 0,floor(n/2),floor(n/2)+1))

先使用row_number()函数对数据从小到大进行排序标号,用count()顺便实现数据总数的记录,假设为n个。如果n为奇数,则取最中间一个值作为中位数,也就是编号为floor(n/2)+1的数,如果n为偶数,需要取中间位置的两个数,也就是floor(n/2)和floor(n/2)+1的两个数。因此可以将floor(n/2)+1作为一个rn的取值,另一个通过判断奇偶性来选择。

当然思路一致,你选择不同的函数实现也是可以的,比如不用if用case when来判断。

方法2:正排倒排来一遍法

思路介绍:

不妨这样想一想,还是根据中位数的概念,一组数据想求中位数,那么这个数或这两个数肯定在一组排序好的数据的中间位置,那是不是正排和倒排的编号会存在某种规律?

假设有一组数:33,25,4,63,18(奇数个),正排和倒排编号之后如下。

假设还有一组数:33,25,4,63,18,22(偶数个),正排和倒排编号之后如下。

观察上述两个示例,会发现由于中位数的独特魅力所在,无论正排还是倒排,对于奇数个数来说,其编号始终不变,而对于偶数个数来说,两个中位数(取均值)的编号相差±1。

按照上述观察结果,就可以得到另一种求中位数的思路,也就是对数据进行正排和倒排,编号,按照奇偶两种条件进行限制,求得编号是这两种条件的一个值或两个值的平均数作为中位数。

示例:

有一组数:1,3,55,8,34,66,42,88,SQL找出中位数。

按照先前的思路,使用相关函数实现:

select avg(num)from (    select num         ,row_number() over(order by num) as rn1        ,row_number() over(order by num desc) as rn2    from tmp)as t where rn1 = rn2 or abs(rn1-rn2) = 1

此处需要注意一个问题,上述SQL代码用MySQL跑时,会报错,需要设置下参数,SET sql_mode='NO_UNSIGNED_SUBTRACTION'

还有,我们不得不考虑这样一种情况,如果待求中位数的数据中存在相等的数怎么办?比如下图的示例,出现了多个重复数据,对于相同的值使用row_number()函数可能不能实现像我们预期那样的正排倒排,此时若按照rn1 = rn2或abs(rn1 - rn2)相差1这两个条进行限制只能得到6,但实际上中位数为2和6的平均数。

因此为了达到预期想要的正排倒排的效果,可以使用主键id跟着要排序的数据进行正排倒排,保证正排和倒排数据的编号走向处处相反。

select avg(num)from (    select id        ,num         ,row_number() over(order by num, idas rn1        ,row_number() over(order by num desc, id descas rn2    from tmp)as t where rn1 = rn2 or abs(rn1-rn2) = 1

难道求中位数只能通过排序?不排序可以找到中位数吗?我们来看看方法三。

方法三:自连接比较法

思路介绍:

我们可以想一想,除了被动排序编号,这些数据是不是可以主动一把?一个数A可以主动去跟别的数作比较,如果比别的数小则+1,比别的数大则-1,这+1,-1加和是不是能表示这个数的“地位”,也就是变相的表征如果按大小排序,是排在什么样的位置上。是不是也就意味着+1,-1加和得到的结果(绝对值)越小,这个数越处于居中位置?

举个例子。

有这么一组数据:1,2,3,4,5,6。按照刚刚描述,对它们分别求取一个加和结果margin和margin的绝对值。

从图上可以看到,3和4对应的margin的绝对值最小,因此它们两个就是居中的数。通过这个例子是否能get此方法?是否能得到某个处于中间位置的值,或某两个处于中间位置的值?

当然上述示例比较简单,再多考虑一下,这种操作对于有重复值的适用吗?

我们看图中这个示例,数据中存在多个重复值,按刚刚的思路,margin绝对值最小的num即为中位数,但此示例中显然不是,2和6的均值才是中位数。

此时要想沿用之前的思路,就得加限制条件,也就是统计下与该数相等的数的个数,记为equal,毕竟个数会影响到num的位置。选出equal大于或等于margin绝对值的num,也就是2和6。

按照目前的思路,使用相关语句实现:

select avg(num)from (    select t1.num         ,abs(sum(sign(t2.num-t1.num))) as margin        ,sum(if(t1.num = t2.num,1,0)) as equal    from     (        select num         from tmp     )as t1     inner join     (        select num         from tmp    )as t2     group by t1.num )as t where equal >= margin

以上便是我今天分享的三种使用SQL进行中位数求取的方法。当然大家也可以考虑下是否存在某些现成的计算分位数的函数,毕竟中位数就是二分位数,直接使用函数可比写SQL来的容易。如此看来,此考题的目的就是考你对于中位数的理解,考你的思路和方法了。

至于与中位数相关的笔试题目我这边就不做赘述了,大家可以去网站上搜搜看,有类似的题目,现实中也有相关应用。比如求各科成绩的中位数、各部门员工薪资的中位数等等。实例有很多,可以自己结合现成函数或上述方法操作看看。

·················END·················

推荐阅读

  1. 我在字节做了哪些事

  2. 写给所有数据人。

  3. 从留存率业务案例谈0-1的数据指标体系

  4. 数据分析师的一周

  5. 超级菜鸟如何入门数据分析?


欢迎长按扫码关注「数据管道」

浏览 46
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报