【窗函数 (SQL)】
在SQL中，窗函数(window function)或分析函数(analytic function)是一个函数，它使用来自一行或多行的值来为每一行返回一个值。 与之形成对比，聚合函数为多行返回单个值。窗口函数有一个OVER子句；任何没有OVER子句的函数都不是窗口函数，而是聚合函数或单行（标量）函数。
==例子1==
例如，这里有一个查询，它使用一个窗口函数来比较每个员工的工资与其部门的平均工资（来自PostgreSQL文档的示例）:
SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsalary;
输出:
depname
avg
----------+-------+--------+----------------------
develop
5020.0000000000000000
develop
5020.0000000000000000
develop
5020.0000000000000000
develop
5020.0000000000000000
develop
5020.0000000000000000
personnel |     5 |   3500 | 3700.0000000000000000
personnel |     2 |   3900 | 3700.0000000000000000
sales
4866.6666666666666667
sales
4866.6666666666666667
sales
4866.6666666666666667
(10 rows)
PARTITION BY子句将行分组，并且该函数分别应用于每个分组。 如果PARTITION BY子句被省略（例如如果我们有一个空的OVER()子句），那么整个结果集被视为单个分组。对于此查询，报告的平均工资将是所有行的平均值。
窗口函数在聚合之后进行评估（例如，在 GROUP BY子句和非窗口聚合函数之后）。
== 语法 ==
根据PostgreSQL文档，窗函数具有下列之一的语法:
function_name (expression , expression ... ) OVER window_name
function_name (expression , expression ... ) OVER ( window_definition )
function_name ( * ) OVER window_name
function_name ( * ) OVER ( window_definition )
其中function_name包括：
*开窗函数
**序号函数
***ROW_NUMBER：顺序排序——1、2、3
***RANK：并列排序，跳过重复序号——1、1、3
***DENSE_RANK：并列排序，不跳过重复序号——1、1、2
**分布函数
***PERCENT_RANK
***CUME_DIST
**前驱后继函数
***LAG(expr,n)：返回当前行的前n行的expr的值
***LEAD(expr,n)：返回当前行的后n行的expr的值
**头尾函数
***FIRST_VALUE(expr)
***LAST_VALUE(expr)
**其他函数
***NTH_VALUE(expr,n)：返回第n行的expr值
***NTILE(n)：将有序数据分为n个桶，记录等级数
*所有聚合函数
其中 window_definition具有语法:
existing_window_name
PARTITION BY expression , ...
ORDER BY expression  USING operator   LAST }  , ...
frame_clause
frame_clause具有下列之一的语法:
{ RANGE | ROWS | GROUPS } frame_start  frame_exclusion
{ RANGE | ROWS | GROUPS } BETWEEN frame_start AND frame_end  frame_exclusion
frame_start与frame_end可以是UNBOUNDED PRECEDING, offset PRECEDING, CURRENT ROW, offset FOLLOWING,  UNBOUNDED FOLLOWING. frame_exclusion可以是EXCLUDE CURRENT ROW, EXCLUDE GROUP, EXCLUDE TIES, EXCLUDE NO OTHERS.
expression指不包含窗函数调用的任何表达式。
注:
* 方括号指可选子句
* 圆括号{}指多种选项的集合，选项之间以竖杠|分割
== 例子2==
窗函数允许访问恰在当前记录之前和之后的记录的数据。 一个窗函数定义当前行周围具有给定行数的帧或窗，并跨窗对数据集执行计算。
NAME |
------------
Aaron|
SELECT
LAG(name, 1)
OVER(ORDER BY name) "prev",
name,
LEAD(name, 1)
OVER(ORDER BY name) "next"
FROM people
ORDER BY name
查询结果为:
==例子3==
# 首先创建虚拟的业务员销售数据
CREATE TABLE Sales
(
idate date,
iname char(2),
sales int
);
# 向表中插入数据
INSERT INTO Sales VALUES
('2021/1/1', '丁一', 200),
('2021/2/1', '丁一', 180),
('2021/2/1', '李四', 100),
('2021/3/1', '李四', 150),
('2021/2/1', '刘猛', 180),
('2021/3/1', '刘猛', 150),
('2021/1/1', '王二', 200),
('2021/2/1', '王二', 180),
('2021/3/1', '王二', 300),
('2021/1/1', '张三', 300),
('2021/2/1', '张三', 280),
('2021/3/1', '张三', 280);
# 查询各月中销售业绩最差的业务员
SELECT * FROM
(SELECT month(idate),iname,sales,
ROW_NUMBER() OVER(PARTITION BY month(idate) ORDER BY sales) as sales_order
FROM Sales) as t
WHERE sales_order=1;
==例子4==
求用户连续登录天数
# 首先创建虚拟的用户登录表，并插入数据
create table user_login
(
user_id varchar(100),
login_time datetime
);
insert into user_login values
(1,'2020-11-25 13:21:12'),
(1,'2020-11-24 13:15:22'),
(1,'2020-11-24 10:30:15'),
(1,'2020-11-24 09:18:27'),
(1,'2020-11-23 07:43:54'),
(1,'2020-11-10 09:48:36'),
(1,'2020-11-09 03:30:22'),
(1,'2020-11-01 15:28:29'),
(1,'2020-10-31 09:37:45'),
(2,'2020-11-25 13:54:40'),
(2,'2020-11-24 13:22:32'),
(2,'2020-11-23 10:55:52'),
(2,'2020-11-22 06:30:09'),
(2,'2020-11-21 08:33:15'),
(2,'2020-11-20 05:38:18'),
(2,'2020-11-19 09:21:42'),
(2,'2020-11-02 00:19:38'),
(2,'2020-11-01 09:03:11'),
(2,'2020-10-31 07:44:55'),
(2,'2020-10-30 08:56:33'),
(2,'2020-10-29 09:30:28');
# 第一种情况：查看每位用户连续登陆的情况。包括每位用户连续登录的情况、查看每位用户最大连续登录的天数、查看在某个时间段里连续登录天数超过N天的用户
select user_id,
min(login_date) start_date,
max(login_date) end_date,
count(login_date) days # 计算每位用户连续登录天数
from (select *,date_sub(login_date, interval irank day) idate # 增加辅助列，以判断用户是否连续登录
from (select *,rank() over(partition by user_id order by login_date) irank # 对每个用户的登录日期排序
from (select distinct user_id, date(login_time) login_date from user_login
# 数据预处理：由于统计的窗口期是天数，对登录时间字段格式转换为日期然后去重（去掉用户一天内多次登录的情况）
) as a
) as b
) as c
group by user_id,idate;
# 第二种情况：计算每个用户最大连续登录天数
# 第三种情况：查看给定时间段内连续登录天数≥5天的用户
# 找出相差天数为5的记录
select distinct user_id
from (select *,datediff(idate5,login_date)+1 as days # 计算当前登录日期与之后第4次登陆的日期的差值
from (select *,lead(login_date,4) over(partition by user_id order by login_date) idate5 # 获取当前登录日期之后第4次登陆的日期
from user_logrin_date)
as a)
as b
where days = 5;
== 历史==
引入了窗函数，其后的标准扩展了其功能。
MySQL从8.0开始引入了窗函数。目前支持的语法为：
# 开窗函数语法
func_name() OVER(PARTITION BY  DESC)
== 参考文献==