百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

MySQL8之窗口函数

toyiye 2024-06-21 12:13 11 浏览 0 评论

作为一名数据分析师,日常工作中经常会遇到类似这样的需求:
怎么样得到各部门工资排名前N名员工列表?查找各部门每人工资占部门总工资的百分比?累计求和如何计算?
对于这样的需求,使用传统的SQL实现起来比较困难。这类需求都有一个共同的特点,需要在单表中满足某些条件的结果集内部做一些函数操作,不是简单的表连接,也不是简单的聚合可以实现的,通常费了大半天时间写出来一堆长长的晦涩难懂的SQL,且性能低下,难以维护。要解决此类问题,最方便的就是使用窗口函数。

1.窗口函数初识

MySQL从8.0开始支持窗口函数,这个功能在大多数据库中早已支持,有的也叫分析函数。

窗口的概念非常重要,它可以理解为记录集合,窗口函数也就是在满足某种条件的记录集合上执行的特殊函数对于每条记录都要在此窗口内执行函数,有的函数随着记录不同,窗口大小都是固定的,这种属于静态窗口;有的函数则相反,不同的记录对应着不同的窗口,这种动态变化的窗口叫滑动窗口。简单的说窗口函数就是对于查询的每一行,都使用与该行相关的行进行计算。

窗口函数也叫OLAP函数(Online Analytical Processing,联机分析处理),可以对数据进行实时分析处理。窗口函数和group by有类似之处,其区别在于窗口会对每个分组之后的数据进行分别操作,而group by一般对分组之后的函数使用聚集函数汇总。

2.窗口函数和普通聚合函数的区别

①聚合函数是将多条记录聚合为一条;窗口函数是每条记录都会执行,有几条记录执行完还是几条。

②聚合函数也可以用于窗口函数。

3.常见的窗口函数

注:‘参数’列说明该函数是否可以加参数。“否”说明该函数的括号内不可以加参数。expr即可以代表字段,也可以代表在字段上的计算,比如sum(col)等。以下相同。

将上述函数按照功能划分,可以把MySQL支持的窗口函数分为如下几类:

  • 序号函数:ROW_NUMBER()、RANK()、DENSE_RANK()
  • 分布函数:PERCENT_RANK()、CUME_DIST()
  • 前后函数:LAG()、LEAD()
  • 头尾函数:FIRST_VALUE()、LAST_VALUE()
  • 其它函数:NTH_VALUE()、NTILE()

4.窗口函数的使用

窗口函数的语法:

window_function_name(expression)

OVER (

[partition_defintion]

[order_definition]

[frame_definition]

)

即:窗口函数名([<字段名>]) over([partition by <分组字段>] [order by <排序字段> [desc]] [<窗口分区>])

窗口函数的一个概念是当前行,当前行属于某个窗口,窗口由over关键字用来指定函数执行的窗口范围,如果后面括号中什么都不写,则意味着窗口包含满足where条件的所有行,窗口函数基于所有行进行计算;如果不为空,则有三个参数来设置窗口:

  • partition by子句:按照指定字段进行分区,两个分区由边界分隔,窗口函数在不同的分区内分别执行,在跨越分区边界时重新初始化。
  • order by子句:按照指定字段进行排序,窗口函数将按照排序后的记录顺序进行编号。可以和partition by子句配合使用,也可以单独使用。
  • frame子句:当前分区的一个子集,用来定义子集的规则,通常用来作为滑动窗口使用。

5.示例代码

  • 建库语句

CREATE database my_exercise_db;

USE my_exercise_db;

  • 创建tb_customer_shopping表和tb_score表

①创建tb_customer_shopping表

CREATE TABLE tb_customer_shopping(
order_id INT COMMENT '订单id',
username VARCHAR(20) COMMENT '顾客姓名',
create_date date COMMENT '购买日期',
cost INT COMMENT '购买金额'
);

向tb_customer_shopping表中添加数据

INSERT INTO tb_customer_shopping VALUES
(1,'Jack','2017-01-01',10),
(2,'Tony','2017-01-02',15),
(3,'Jack','2017-02-03',23),
(4,'Tony','2017-01-04',29),
(5,'Jack','2017-01-05',46),
(6,'Jack','2017-04-06',42),
(7,'Tony','2017-01-07',50),
(8,'Jack','2017-01-08',55),
(9,'King','2017-04-08',62),
(10,'King','2017-04-09',68),
(11,'Paul','2017-05-10',12),
(12,'King','2017-04-11',75),
(13,'Paul','2017-06-12',80),
(14,'King','2017-04-13',94);

②创建 tb_score表

CREATE TABLE tb_score(
stu_id int,
name VARCHAR(20),
subject VARCHAR(10),
score INT);

向tb_score表中添加数据

INSERT INTO tb_score VALUES
(1,'孙悟空','语文',87),
(1,'孙悟空','数学',100),
(1,'孙悟空','英语',68),
(2,'唐僧','语文',94),
(2,'唐僧','数学',56),
(2,'唐僧','英语',84),
(3,'沙僧','语文',87),
(3,'沙僧','数学',97),
(3,'沙僧','英语',84),
(4,'八戒','语文',65),
(4,'八戒','数学',85),
(4,'八戒','英语',78),
(5,'蜘蛛侠','语文',55),
(5,'蜘蛛侠','数学',97),
(5,'蜘蛛侠','英语',98),
(6,'美国队长','语文',56),
(6,'美国队长','数学',99),
(6,'美国队长','英语',87),
(7,'钢铁侠','语文',94),
(7,'钢铁侠','数学',100),
(7,'钢铁侠','英语',85);

1. 序号函数:ROW_NUMBER()、RANK()、DENSE_RANK()

  • 用途:显示分区中的当前行号,对查询结果进行排序.

ROW_NUMBER():顺序排序——1、2、3
RANK():并列排序,跳过重复序号——1、1、3
DENSE_RANK():并列排序,不跳过重复序号——1、1、2

  • 应用场景:常用

执行如下SQL语句,代码运行结果如图所示:

SELECT stu_id,name,subject,score,
ROW_NUMBER() OVER (PARTITION BY subject ORDER BY score DESC) AS ROW_NUM,
DENSE_RANK() OVER (PARTITION BY subject ORDER BY score DESC) AS DENSE_RK,
RANK() OVER (PARTITION BY subject ORDER BY score DESC) AS RK
FROM tb_score ts ;

2. 分布函数:PERCENT_RANK()、CUME_DIST()

  • 用途:每行按照公式(rank-1) / (rows-1)进行计算。其中,rank为RANK()函数产生的序号,rows为当前窗口的记录总行数
  • 应用场景:不常用

给窗口指定别名:WINDOW w AS (PARTITION BY stu_id ORDER BY score)

rows = 3

执行如下SQL语句,代码运行结果如图所示:

SELECT
RANK() OVER w AS rk,
PERCENT_RANK() OVER w AS prk,
stu_id, name, score
FROM tb_score
WHERE stu_id = 1
WINDOW w AS (PARTITION BY stu_id ORDER BY score);

CUME_DIST()

  • 用途:分组内小于、等于当前rank值的行数 / 分组内总行数
  • 应用场景:查询小于等于当前成绩(score)的比例

cm1:没有分区,则所有数据均为一组,总行数为6
cm2:按照name分成了两组,行数各为3

注意:CUME_DIST、PERCENT_RANK均不支持WINDOW子句S(between...and...)

执行如下SQL语句,代码运行结果如图所示:

SELECT stu_id, name, score,
CUME_DIST() OVER (ORDER BY score) AS cm1,
CUME_DIST() OVER (PARTITION BY name ORDER BY score) AS cm2
FROM tb_score
WHERE name IN ('孙悟空','唐僧');

3. LAG和LEAD分析函数可以在同一次查询中取出同一字段的前N行的数据(LAG)和后N行的数据(LEAD)作为独立的列

在实际应用当中,若要用到取今天和昨天的某字段差值时,LAG和LEAD函数的应用就显得尤为重要。当然,这种操作可以用表的自连接实现,但是LAG和LEAD与LEFT JOIN、RIGHT JOIN等自连接相比,效率更高,SQL更简洁。下面我就对这两个函数做一个简单的介绍。

函数语法如下:

lag(exp_str,offset,defval) OVER(PARTITION BY …ORDER BY …)

lead(exp_str,offset,defval) OVER(PARTITION BY …ORDER BY …)

参数说明:

exp_str是字段名

offset是偏移量,即是上1个或上N个的值,假设当前行在表中排在第10行,则offset 为3,则表示我们所要找的数据行就是表中的第7行(即10-3=7)。

defval默认值,当两个函数取上N/下N个值,当在表中从当前行位置向前数N行已经超出了表的范围时,LAG()函数将defval这个参数值作为函数的返回值,若没有指定默认值,则返回NULL,那么在数学运算中,总要给一个默认值才不会出错。

执行如下SQL语句,代码运行结果如图所示:

SELECT
order_id ,username ,create_date ,cost ,
LAG(cost,1,0) OVER(PARTITION BY username ORDER BY create_date) cost_lag,
LEAD(cost,1,0) OVER(PARTITION BY username ORDER BY create_date) cost_lead
FROM tb_customer_shopping tcs ;

4. 头尾函数:FIRST_VALUE(expr)、LAST_VALUE(expr)

  • 用途:返回第一个(FIRST_VALUE(expr))或最后一个(LAST_VALUE(expr))expr的值
  • 应用场景:截止到当前的cost,按照日期排序查询第1个和最后1个cost值

执行如下SQL语句,代码运行结果如图所示:

SELECT order_id,username,create_date,cost,
FIRST_VALUE(cost) OVER(PARTITION BY username ORDER BY create_date DESC) first_cost,
LAST_VALUE(cost) OVER(PARTITION by username ORDER BY create_date DESC) last_cost
FROM tb_customer_shopping tcs;

FIRST_VALUE()的结果容易理解,直接在结果的所有行记录中输出同一个满足条件的首个记录;

LAST_VALUE()默认统计范围是 rows between unbounded preceding and current row,也就是取当前行数据与当前行之前的数据的比较。

那么如果我们直接在每行数据中显示最后的那个数据,需在order by 条件的后面加上语句:rows between unbounded preceding and unbounded following , 也就是前面无界和后面无界之间的行比较。

执行如下SQL语句,代码运行结果如图所示:

SELECT order_id,username,create_date,cost,
FIRST_VALUE(cost) OVER(PARTITION BY username ORDER BY create_date DESC) first_cost,
LAST_VALUE(cost) OVER(PARTITION BY username ORDER BY create_date DESC rows between unbounded preceding and unbounded following) last_cost
FROM tb_customer_shopping;

5.其它函数:NTH_VALUE(expr, n)、NTILE(n)

① NTH_VALUE(expr, n)

其中NTH_VALUE(expr, n)中的第二个参数是指这个函数取排名第几的记录,返回窗口中第n个expr的值。expr可以是表达式,也可以是列名。

执行如下SQL语句,代码运行结果如图所示:

SELECT order_id,username,create_date,cost,
NTH_VALUE(cost,3) OVER(ORDER BY username ASC) nth_cost
FROM tb_customer_shopping;

② NTILE(n)函数

  • 用途:将分区中的有序数据分为n个等级,记录等级数
  • 应用场景:将每个顾客按cost分成4组

NTILE(n)函数在数据分析中应用较多,比如由于数据量大,需要将数据平均分配到n个并行的进程分别计算,此时就可以用NTILE(n)对数据进行分组(由于记录数不一定被n整除,所以数据不一定完全平均),然后将不同桶号的数据再分配。

执行如下SQL语句,代码运行结果如图所示:

SELECT
NTILE(4) OVER w AS nf,
order_id, username, cost
FROM tb_customer_shopping tcs
WHERE username IN ('Jack','King')
WINDOW w AS (PARTITION BY username ORDER BY cost);

6.聚合函数作为函数函数

在窗口中每条记录动态应用聚合函数(sum/avg/max/min/count),可以动态计算在指定的窗口内的各种聚合函数值。
示例:每个用户截止到当前的累计购买金额/平均购买金额/最大购买金额/最小购买金额/购买数量。

执行如下SQL语句,代码运行结果如图所示:

SELECT
*,
SUM(cost) OVER(PARTITION BY username ORDER BY create_date) sum_cost,
AVG(cost) OVER(PARTITION BY username ORDER BY create_date) avg_cost,
MAX(cost) OVER(PARTITION BY username ORDER BY create_date) max_cost,
MIN(cost) OVER(PARTITION BY username ORDER BY create_date) min_cost,
COUNT(cost) OVER(PARTITION BY username ORDER BY create_date) count_cost
FROM tb_customer_shopping;

6.总结

MySQL8.0中加入了窗口函数的功能,这一点方便了SQL的编写,可以说是MySQL8.0的亮点之一,窗口函数功能很强大,对于一些使用常规思维无法实现的SQL需求,它都能帮我们实现,大家尝试一下窗口函数吧,相信会有意想不到的收获。


相关阅读:

MySQL之全局序列

Mysql子查询详解

MySQL之主从复制

MySQL权限与安全

MySQL的配置详解

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码