MySql 8.0 窗口函数

471 阅读4分钟

概念

窗口:可以理解为记录集合。
窗口函数:在满足某种条件的记录集合上执行的特殊函数。对于每条记录都要在此窗口内执行函数。有的函数,随着记录不同,窗口大小都是固定的,这种属于静态窗口;有的函数则相反,不同记录对应着不同的窗口,这种动态变化的窗口叫滑动窗口。
窗口函数和聚合函数:聚合函数是多行记录聚合成一行,而窗口函数则是多行聚合为相同的行数,每一行都都会多出一个新列(这个新列就是窗口函数进行运算后的结果)。聚合函数可以作为窗口函数

记住两点:
1,窗口是记录集合,可以是静态窗口,可以是滑动窗口。
2,使用窗口函数后,每一行记录都会使用窗口函数在窗口内(静态窗口或滑动窗口)进行计算产生新的一列。

例子:
新建一张订单表 order_tab

CREATE TABLE `order_tab` (
  `order_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_no` varchar(100) CHARACTER SET armscii8 COLLATE armscii8_general_ci NOT NULL,
  `amount` double(255,0) NOT NULL,
  `create_date` datetime NOT NULL,
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=armscii8

插入一些数据

INSERT INTO order_tab(user_no, amount, create_date) VALUES('001', 100, '2018-01-01 00:00:00');
INSERT INTO order_tab(user_no, amount, create_date) VALUES('001', 300, '2018-01-02 00:00:00');
INSERT INTO order_tab(user_no, amount, create_date) VALUES('001', 500, '2018-01-02 00:00:00');
INSERT INTO order_tab(user_no, amount, create_date) VALUES('001', 800, '2018-01-03 00:00:00');
INSERT INTO order_tab(user_no, amount, create_date) VALUES('001', 900, '2018-01-04 00:00:00');

INSERT INTO order_tab(user_no, amount, create_date) VALUES('002', 500, '2018-01-03 00:00:00');
INSERT INTO order_tab(user_no, amount, create_date) VALUES('002', 600, '2018-01-04 00:00:00');
INSERT INTO order_tab(user_no, amount, create_date) VALUES('002', 300, '2018-01-10 00:00:00');
INSERT INTO order_tab(user_no, amount, create_date) VALUES('002', 800, '2018-01-16 00:00:00');
INSERT INTO order_tab(user_no, amount, create_date) VALUES('002', 800, '2018-01-22 00:00:00');

image.png

窗口函数:

  1. ROW_NUMBER() 分区中的当前行号 查询每个用户最新一笔订单:
SELECT* FROM
(
	SELECT ROW_NUMBER() OVER(PARTITION BY user_no ORDER BY create_date desc) as row_num, 
	order_id, 
	user_no, 
	amount, 
	create_date 
	FROM order_tab
)t WHERE row_num=1;

image.png

ROW_NUMBER()后面的OVER是关键字,用来指定窗口函数执行的窗口范围(窗口记录集合),如果括号中什么都不写,则意味着窗口范围是所有行;如果不为空,则支持以下4种语法。

  • window_name,给窗口指定一个别名,别名使得代码清晰易读 上面的例子可以使用别名
SELECT* FROM
(
	SELECT ROW_NUMBER() OVER w as row_num, 
	order_id, 
	user_no, 
	amount, 
	create_date 
	FROM order_tab
	WINDOW w AS(PARTITION BY user_no ORDER BY create_date desc)
)t WHERE row_num=1;
  • PARTITION子句 窗口(所有行记录)按照某个字段分组,窗口函数在不同的分组上分别执行。上面的例子就是按照用户id进行分组,分为了两组,在每个用户id分组上,执行从1开始的顺序编号。

  • ORDER BY子句 窗口(所有行记录)按照某个字段进行排序,窗口函数将在排序后的记录上执行,既可以配合PARTITION子句使用,也可以单独使用。

  • FRAME子句 frame是当前分区的一个子集,用来创建滑动窗口。

上面的例子可以看到,创建了两个分组(窗口,数据集),窗口函数分别在两个分组上执行。

使用frame子句,查询每个用户,按照时间倒序,当前订单和前后两个订单的平均金额:

image.png

这里,首先按照用户分组形成两个窗口(数据集),按照在两个窗口上使用frame子句形成一个滑动窗口(当前订单和前后两个订单),让聚合函数avg()在这样一个滑动窗口上执行。
滑动窗口的范围指定,有下面两种方式:

image.png

  1. RANK()和DENSE_RANK()函数 和ROW_NUMBER()函数类似,只是在遇到列相同值时,排名序号有所不同。
    RANK()会产生相同的序号,同时可能产生序号间隙;DENSE_RANK()也会产生相同的序号,但不会产生序号间隙。

下面这个例子中,用户002按照价格降序排序的时候,前两条记录的amount金额都是800。 image.png

以下窗口函数略过,可查阅《深入浅出MySQL 数据库开发 优化与管理维护 第3版》
3. PERCENT_RANK()和CUME_DIST()函数 4. NFILE(N)函数 5. NTH_VALUE(expr, N)函数 6. LAG(expr, N)和LEAD(expr, N)函数 7. FIRST_VALUE(expr)和LAST_VALUE(expr)函数 8. 聚合函数可作为窗口函数

image.png

参考书籍:
《深入浅出MySQL 数据库开发 优化与管理维护 第3版》