导读:计算中位数可能是小学的内容,然而在数据库查询中实现却并不是一件容易的事。我们今天就来看看都有哪些方法可以实现。

注:本文所用MySQL版本无限制,所列题目均来源于LeetCode。

LeetCode数据库题目中关于中位数的主要有两道题,难度都是hard级别。两道题目无论是出现频率还是相关企业标签数,都属于比较靠前的位置,包括题解和讨论数量也是如此,足以见其热门程度。

569# 员工薪水中位数

题目描述:

预期答案:

解法1

既然是求解中位数,我们首先想到的是根据中位数的定义进行求解:奇数个数字时,中位数是中间的数字;偶数个数字时,中位数中间两个数的均值。本题不进行求解均值,而是将两个中位数全部显示。

根据定义,为了查询中位数,我们需要知道3点信息:

  • 总数是奇数个还是偶数个
  • 待查找数字总数
  • 每个数字的排序编号

前两点信息在MySQL中非常简单,只需简单的count计数即可,而排序编号则需要借助辅助方法。在MySQL8.0以上版本引入了窗口函数后非常容易实现,但以前的版本则仅可通过自定义变量的方式获得排序值。这里如何对员工薪水进行分组排序不再展开,具体可参考历史文章一文解决所有MySQL分类排名问题。

在有了排名和数字总数之后,如何判断是中位数呢?这里计数字总数为N,则

  • N为奇数,中位数排序编号是(N+1)/2=N/2+0.5
  • N为偶数,中位数排序编号是N/2和N/2+1

进一步地,N为奇数和N为偶数是互斥的,求解出的中位数排序编号也是互斥的,也就是说3个排序编号不会同时取得整数,从而可以不加区分的直接判断即可。

查询SQL语句:

SELECT  e1.Id, e1.Company, e1.Salary
FROM  (SELECT Id, Company, Salary, @rnk:=if(@pre=Company, @rnk+1, 1) rnk, @pre:=Company  FROM Employee, (SELECT @rnk:=0, @pre:=null)init  ORDER by Company, Salary, Id)e1   JOIN   (SELECT Company, count(*) cnt FROM Employee GROUP by Company) e2  using(Company)
WHERE e1.rnk in (cnt/2+0.5, cnt/2, cnt/2+1)

查询效率:

解法2

除了根据中位数的排序编号来定位其位置,实际上还可以换种思路但仍然是在其排序编号上做文章:如果一个数是中位数,那么就意味着正序和逆序时其位置是一致的:更严谨的说,奇数个数字是正逆序排序一致,偶数个数字时,两中位数顺序要互换一下,也就是相差为1。进而,我们发现无论数字总数是奇数还是偶数,中位数的正逆排序相差要么为0,要么为1。根据这一性质,我们分别实现正逆两遍排序,然后判断数字的排序编号即可。

查询SQL语句:

SELECT  e1.Id, e1.Company, e1.Salary
FROM  (SELECT Id, Company, Salary, @rnk:=if(@pre=Company, @rnk+1, 1) rnk, @pre:=Company  FROM Employee, (SELECT @rnk:=0, @pre:=null)init  ORDER by Company, Salary, Id)e1   JOIN   (SELECT Id, Company, Salary, @rnk:=if(@pre=Company, @rnk+1, 1) rnk, @pre:=Company  FROM Employee, (SELECT @rnk:=0, @pre:=null)init ORDER by Company, Salary DESC, Id DESC)e2 on e1.Id=e2.Id
WHERE abs(e1.rnk - e2.rnk)<=1

查询效率:

解法3

前2种解法都是根据中位数的定义在数字排序编号上作文章,下面是一个对中位数性质更深的理解(摘抄自官方题解)

根据定义,我们来找一下 [1, 3, 2] 的中位数。首先 1 不是中位数,因为这个数组有三个元素,却有两个元素 (3,2) 大于 1。3 也不是中位数,因为有两个元素小于 3。对于 2 来说,大于 2 和 小于 2 的元素数量是相等的,因此 2 是当前数组的中位数。当数组长度为 偶数,且元素唯一时,中位数等于排序后 中间两个数 的平均值。对这两个数来说,大于当前数的数值个数跟小于当前数的数值个数绝对值之差为 1,恰好等于这个数出现的频率。
结论:不管数组长度是奇是偶,也不管元素是否唯一,中位数出现的频率一定大于等于 大于它的数小于它的数 的绝对值之差。

好吧,力扣的官方题解读起来总是这么生涩。不过细品之下,我们还是可以发现这个结论是对的。【好像说了句废话】

根据中位数的这一性质,可以写出如下查询语句:

SELECT  e1.Id, e1.Company, e1.Salary
FROM  Employee e1, Employee e2
WHERE  e1.Company = e2.Company
GROUP BY e1.Company , e1.Salary
HAVING SUM(e1.Salary = e2.Salary) >= ABS(SUM(SIGN(e1.Salary - e2.Salary)))
ORDER BY e1.Id

查询效率:

实际上,虽然3种解法均为两表关联,但由于解法3中涉及到相对更为复杂的计算,其效率竟然要比解法1和解法2中低太多。

所以,不妨想想奥卡姆剃刀原理,大道至简、大巧不工、简单之美!

571# 给定数字的频率查询中位数

刚才一道题是对给定的一组数字查询中位数,顶多也就是要进行分组后查询中位数。那如果给定的数字不是数字全样本,而是数字+频率呢?

题目描述:

注:与前一题不同,本题中如果中位数有两个,返回的是一个均值。

解法1

这一题乍一看还是挺懵的,但有了第一题解法3中的结论,似乎它就是为这一题做的铺垫:这不刚好就是提供的数字及其频率吗?对比其小的数字频率求和就是比其小的数字个数,类似的也可以得到比其大的数字个数。

这样的想法其实非常适合窗口函数,如果是在8.0以上版本,那么如下SQL语句可谓是简洁优雅:

SELECT  number
FROM  (SELECT number, Frequency,  sum( Frequency ) over (rows BETWEEN unbounded preceding AND current ROW ) cnt1,  sum( Frequency ) over (rows BETWEEN current ROW AND unbounded following ) cnt2   FROM  numbers ) tmp
WHERE Frequency >= abs(cnt2 - cnt1)

其中:cnt1为当前行之前的累计个数(含当前行),cnt2为当前行之后的累计个数(含当前行),进而cnt2-cnt1等于比其大的数字和比其小的数字个数之差。

当然,当前LeetCode OJ是5.6版本,MySQL也不能使用窗口函数。此时,可以简单的通过自定义变量得到实现:

SELECT  avg(number) median
FROM (SELECT number, Frequency, @cnt:=@cnt+Frequency cnt FROM numbers, (SELECT @cnt:=0)initORDER BY number)tmp1, (SELECT sum(Frequency) total FROM numbers)tmp2
WHERE Frequency>=abs(total-2*cnt+Frequency)

类似的,这里:

  • cnt为当前行之前的累计数字个数(含当前行),cnt-Frequency为不含当前行的数字个数
  • total为总的数字个数,total-cnt即为当前行之后的数字个数(不含当前行)
  • total-cnt - cnt+Frequency即为需要求的差值

查询效率:

解法2

前面的方法是借助了中位数的一个性质,实话说还是不够直观。那么,如果仍然沿用中位数排序编号的规律,是否可以用于本题的SQL查询呢?

当然可以。实际上,根据数字及频率,可以稍微变形得到数字排序编号的首末区间,然后判断中位数的编号存在于哪个数字的首末区间即可找到中位数。

带着这一想法,我们首先写出如下SQL语句来获得数字的首末区间:

SELECT  number, frequency, @beg:=@end+1 AS beg, @end:=@beg+frequency-1 AS end
FROM  numbers, (SELECT @beg := 0, @end :=0) init
ORDER BY number 

得到如下中间结果:

然后,对中位数位置的三个可能取值(即N/2, N/2+0.5, N/2+1)分别判断是否存在首末区间,进而判断是否是中位数:

SELECT   avg(number) median
FROM  ( SELECT   number, frequency,  @beg := @end+1 AS beg,  @end := @beg+frequency-1 AS end   FROM   numbers, (SELECT @beg := 0, @end :=0) init   ORDER BY  number  ) t1, ( SELECT sum(frequency) cnt FROM numbers ) t2
WHERE (cnt/2 BETWEEN beg AND end) or (cnt/2+0.5 BETWEEN beg AND end) or (cnt/2+1 BETWEEN beg AND end)

查询效率:

解法3

利用中位数的排序值可以判断,利用正逆序的差值应该也可以。仍然是通过正逆两遍排序得到每个数字的两组首末区间,然后判断两个区间在相差1范围内是否存在交集即可。

查询SQL语句:

SELECT   avg(number) median
FROM  ( SELECT   number, frequency,  @beg1 := @end1+1 AS beg,    @end1 := @beg1+frequency-1 AS end   FROM   numbers, (SELECT @beg1 := 0, @end1 :=0) init   ORDER BY  number  ) t1 JOIN ( SELECT  number, frequency,  @beg2 := @end2+1 AS beg,    @end2 := @beg2+frequency-1 AS end  FROM  numbers, (SELECT @beg2 := 0, @end2 :=0) init  ORDER BY number desc ) t2 using(number)
WHERE t1.beg BETWEEN t2.beg-1 and t2.end+1 or t1.end BETWEEN t2.beg-1 and t2.end+1

查询效率:

我们发现,虽然解法3写起来相对复杂,但效率居然是最高的。不过个人还是比较喜欢解法2,即简单的根据中位数排序编号来判断,简单高效易懂。

以上就是LeetCode中两道关于中位数题目的几种解法,当然,肯定还有更多更好的解法,这里也只是简单探讨以作抛砖引玉。

mysql查询id为偶数_MySQL中查询中位数?相关推荐

  1. mysql删除重复文章标题_MySQL中查询、删除重复记录的方法大全

    前言 本文主要给大家介绍了关于MySQL中查询.删除重复记录的方法,分享出来供大家参考学习,下面来看看详细的介绍: 查找所有重复标题的记录: 一.查找重复记录 1.查找全部重复记录 2.过滤重复记录( ...

  2. mysql删除重复的判断_MySQL中查询、删除重复记录一共有多少种方法?

    前言 本文主要给大家介绍了关于MySQL中查询.删除重复记录的方法,分享出来供大家参考学习,下面来看看详细的介绍: 查找所有重复标题的记录: select title,count(*) as coun ...

  3. mysql去掉两个最高分_MySQL中查询、删除重复记录的方法大全

    前言 本文主要给大家介绍了关于MySQL中查询.删除重复记录的方法,分享出来供大家参考学习,下面来看看详细的介绍: 查找所有重复标题的记录: select title,count(*) as coun ...

  4. mysql 怎么查询慢sql语句_Mysql中 查询慢的 Sql语句的记录查找

    Mysql中 查询慢的 Sql语句的记录查找 慢查询日志 slow_query_log,是用来记录查询比较慢的sql语句,通过查询日志来查找哪条sql语句比较慢,这样可以对比较慢的sql可以进行优化. ...

  5. mysql 查询记录成纵向_Mysql中查询(数据库中的)纵向转(查询结果显示为)横向.

    1.在mysql环境,创建数据库表, create table test_data1(id int, name VARCHAR(20), day VARCHAR(20)) 2.插入测试数据, inse ...

  6. mysql查找有小数点的数据_MySQL中查询中位数?

    导读:计算中位数可能是小学的内容,然而在数据库查询中实现却并不是一件容易的事.我们今天就来看看都有哪些方法可以实现. 注:本文所用MySQL版本无限制,所列题目均来源于LeetCode. LeetCo ...

  7. mysql多表查询连接的种类_MySQL中基本的多表连接查询教程

    一.多表连接类型1. 笛卡尔积(交叉连接) 在MySQL中可以为CROSS JOIN或者省略CROSS即JOIN,或者使用','  如: 由于其返回的结果为被连接的两个数据表的乘积,因此当有WHERE ...

  8. mysql查询动态表名的数据类型_Mysql中查询某个数据库中所有表的字段信息

    前言 有时候,需要在数据库中查询一些字段的具体信息,而这些字段又存在于不同的表中,那么我们如何来查询呢? 在每一个数据库链接的information_schema数据库中,存在这样一张表--COLUM ...

  9. mysql的英文字母_MySQL中查询的有关英文字母大小写问题的分析

    mysql数据库在做查询时候,有时候是英文字母大小写敏感的,有时候又不是的,主要是由mysql的字符校验规则的设置决定的,通常默认是不支持的大小写字母敏感的. 1. 什么是字符集和校验规则? 字符集是 ...

最新文章

  1. debian linux修改语言,Debian Linux系统下英文系统切换为中文
  2. Python r‘‘, b‘‘, u‘‘, f‘‘ 的含义
  3. 第一个Python程序hello.py提示出现File stdin,line 1错误
  4. this关键字+super关键字
  5. mysql插入图片数据
  6. 改变网页标签图片(favicon)
  7. 计算机常用主题词,标引主题词的目的是( )。A.反映文件的全部内容B.便于计算机检索和管理C.供归_考题宝...
  8. idea junit 测试看不到控制台报错信息_高手都这么给 Spring MVC 做单元测试!
  9. 设计一个4*4魔方程序,让魔方的各行值的和等于各列值的和,并且等于两对角线的和,例如以下魔方,各行各列及两对角线值的和都是64.
  10. 随想录(mac下c、c++的编译方法)
  11. 安装office2010出现了错误,提示要安装MSXML6.10.1129.0解决方法
  12. 最简单的零成本创业模式
  13. 《圈圈教你玩USB》 第七章 USB MIDI键盘 看书笔记
  14. 几种常用的JSON解析工具的使用
  15. android 刷机 备份,盘点和对比 Android刷机前的数据备份
  16. Java多线程系列--【JUC集合07】- ArrayBlockingQueue
  17. 5.5 Go语言项目实战:多人聊天室
  18. 候选码、主码、外码、全码、超码
  19. java redis 实现pv uv_redis实战-记录PV与UV
  20. 计算机考研初试350分什么水平,考研分数,考研350分什么水平!

热门文章

  1. java命令行参数_一个 java 命令行参数顺序的坑
  2. php遗漏,PHP被遗漏的执行函数
  3. python web框架哪个好_盘点:9款流行Web框架及其优缺点
  4. HNU 实验五松雅的旅馆
  5. 【文献阅读】ResNet-Deep Residual Learning for Image Recognition--CVPR--2016
  6. Java 1.1.3 修改字符串、不可变字符串
  7. Python中替换元素
  8. C语言实现2048小游戏---粤嵌GE6818嵌入式系统实训
  9. Unable to find vcvarsall.bat
  10. 原来理解 AOP 可以这么简单