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

注:本文所用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)init

ORDER 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查找有小数点的数据_MySQL中查询中位数?相关推荐

  1. mysql查询id为偶数_MySQL中查询中位数?

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

  2. mysql delete 表关联删除数据_MYSQL中delete删除多表数据与删除关联数据

    在mysql中删除数据方法有很多种,最常用的是使用delete来删除记录,下面我来介绍delete删除单条记 录与删除多表关联数据的一些简单实例. 1.delete from t1 where 条件 ...

  3. mysql存储数组类型的数据_mysql中怎么存储数组

    展开全部 SQL没有数组这种类型,数组是一种数据结构的概念,跟关系型mysql数据存储32313133353236313431303231363533e78988e69d833133343362313 ...

  4. mysql如何更新两条数据_mysql根据查询结果批量更新多条数据(插入或更新)

    mysql根据查询结果批量更新多条数据(插入或更新) 1.1 前言 mysql根据查询结果执行批量更新或插入时经常会遇到1093的错误问题.基本上批量插入或新增都会涉及到子查询,mysql是建议不要对 ...

  5. mysql将时间戳转换成日期_mysql 中查询时如何将时间戳转换为日期格式 / 日期格式转换为时间戳...

    在数据库的使用中,经常需要按指定日期来查询记录,以便于统计,而在数据库中,有很多存储的是时间戳, 也有的直接存日期,查询的时候可能不是那么好弄. mysql提供了两个函数: from_unixtime ...

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

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

  7. mysql重做日志恢复数据_MySQL中重做日志,回滚日志,以及二进制日志的简单总结...

    MySQL中有六种日志文件, 分别是:重做日志(redo log).回滚日志(undo log).二进制日志(binlog).错误日志(errorlog).慢查询日志(slow query log). ...

  8. mysql约束添加删除数据_mysql中约束的添加,修改,与删除

    MySQL中的约束,添加约束,删除约束,以及其他的一些修饰: 一.NOT NULL(非空约束) 添加非空约束 1)建表时直接添加 CREATE TABLE t_user(user_id INT(10) ...

  9. mysql中如何统计数据_mysql中的数据统计方法

    1 数据统计 使用COUNT()函数计算表中的数据数目(比如emp表中的员工数目) mysql> select count(*) from emp; 查询结果如下: +----------+ | ...

最新文章

  1. 重构实践:体验interface的威力(一)
  2. mysql登录服务器报错_mysql登录服务器报错
  3. python怎么导入包-python怎样导入包
  4. [云炬创业基础笔记]第十章企业的利润计划测试6
  5. html text width,HTML5 Text Canvas rotate in case text width is larger than maximum width allowed
  6. HDU - 4902 Nice boat(线段树)
  7. 计算机模型机设计实验报告,基本模型机设计与实现 实验报告
  8. iOS开发——MBProgressHUD 与 SVProgressHUD iOS提示框的优缺点
  9. (3)Python3笔记之变量与运算符
  10. ios点击大头针气泡不弹出_iOS高德地图之自定义大头针and泡泡view
  11. Linux系统开机自动加载驱动module
  12. word删除分节符后之前的格式乱了_办公室高级技能之Word邮件合并拆分
  13. 新编译的GDAL1.9 C/C++ C# Python版本
  14. mybatis添加方法可以传map吗_Mybatis创建方式二
  15. C语言 用矩形法计算定积分∫(0—1)sinxdx、∫(-1—1)cosxdx、∫(0—2)e^xdx
  16. Win11 Windows聚焦不更新了怎么解决?聚焦锁屏图片不更换怎么办
  17. 阿里入局,通义千问备受期待
  18. 2021-“新“的开源项目之handpose_x(手势识别交互)
  19. 短视频抖音账号矩阵seo优化系统技术代开发
  20. 2022年建筑设计中效果图渲染常见的7个错误

热门文章

  1. Windows 10 再香,国内超一半用户「死守」停更的 Windows 7
  2. SQL 已死,NoSQL 才是王道?|原力计划
  3. 这个东西可以温暖你想打BUG的心......
  4. 物联网 ToB 的背后,开发者应了解什么?| CSDN 博文精选
  5. 传海思为 PC 开发 CPU/GPU ;小米将发布第二款 5G 手机;Firefox 68.0.2 发布 | 极客头条...
  6. 还在担心快应用没流量?全场景新玩法来袭!
  7. Golang 之轻松化解 defer 的温柔陷阱
  8. 程序员亲身体验的学历之痛
  9. 福利 | 2018 年,程序员全新的技术之路
  10. 字节跳动面试:java后端面试宝典