MySQL窗口函数详解

一、窗口函数的组成部分

(1)窗口函数本身:这是执行计算的函数,如 SUM(), AVG(), ROW_NUMBER() 等。

(2)OVER子句:定义了窗口函数的计算范围。它由三部分组成:

  ①、PARTITION BY:将数据集分成多个独立的组,每个组内部进行计算。如果省略,整个数据集被视为一个单一的分区。

  ②、ORDER BY:在每个分区内对数据进行排序,这会影响某些窗口函数的计算结果,如 ROW_NUMBER(), RANK() 等。

  ③、ROWS或RANGE:定义了窗口的物理大小,即函数作用的行数范围。ROWS 是基于行号的,而 RANGE 是基于值的范围。

(3)窗口框架(Frame):在某些窗口函数中,你可以通过窗口框架进一步细化窗口的大小。窗口框架可以是静态的(如 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)或动态的(如 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)。

 

二、窗口函数的类型

(1)排名函数:如 ROW_NUMBER(), RANK(), DENSE_RANK(),它们为分区内的每一行分配一个唯一的序号或排名。

(2)聚合函数:如 SUM(), AVG(), MIN(), MAX() ,它们计算分区内所有行的聚合值。

(3)偏移函数:如 LEAD(), LAG(),它们允许你访问当前行的前面或后面的行的值。

(4)首尾函数:如 FIRST_VALUE(),LAST_VALUE(),NTH_VALUE() ,它们允许你访问当前窗口的首行、末行、第N行的值。

(5)分布函数:如 PERCENT_RANK(),NTILE(),CUME_DIST(),它们用于分析数据的分布特征或分组排名‌。

 

分类

函数

功能

排名窗口

ROW_NUMBER()

计算行号。为每一行分配一个唯一的整数,从1开始递增。

RANK()

计算排名,排名可能不连续。对每一行进行排名,当遇到相同值时,排名会跳过一些数字。

DENSE_RANK()

计算排名,排名是连续的。对每一行进行排名,但不会跳过排名数字,即使遇到相同值。

聚合窗口

MAX(列名)

计算窗口中的最大值。

MIN(列名)

计算窗口中的最小值。

SUM(列名)

对窗口中的数据求和。

AVG(列名)

对窗口中的数据求平均值。

COUNT(列名)

计算窗口中的记录数。

STDDEV(列名)

计算总体标准差。使用公式 S = sqrt(sum((x_i - x_bar)^2) / N),其中 N 是样本中的数据点数量。

STDDEV_SAMP(列名)

计算样本标准差。使用公式 S = sqrt(sum((x_i - x_bar)^2) / (N - 1)),其中 N 是样本中的数据点数量。

偏移(前后取值)窗口

LAG(列名,行数)

取当前行往前(朝分区头部方向)第N行数据的值。

LEAD(列名,行数)

取当前行往后(朝分区尾部方向)第N行数据的值。

首尾取值窗口

FIRST_VALUE(列名)

取窗口内第一行的值。

LAST_VALUE(列名)

取窗口内最后一行的值。

NTH_VALUE(列名,N)

取窗口内第N行的值。

分布窗口

PERCENT_RANK()

计算行的百分比排名。输出百分比格式。

CUME_DIST()

计算行相对于所有其他行的累积分布。

NTILE(N)

将数据顺序切分成N等份,返回数据所在等份的编号(从1到N)。

 

三、窗口函数的分区和排序

(1)PARTITION BY:这个子句将结果集分成多个独立的分区,每个分区都是一个独立的数据集,窗口函数在每个分区上独立计算。例如,如果你按部门对员工数据进行分区,每个部门将有自己的窗口函数计算结果。

(2)ORDER BY:这个子句在每个分区内对行进行排序。对于排名函数,ORDER BY 子句决定了行的排名顺序。对于聚合函数,ORDER BY 子句决定了窗口的计算顺序。

四、窗口函数的窗口框架

(1)ROWS:基于行号的窗口框架,可以是固定的行数(如 ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING),也可以是相对于当前行的位置(如 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)。

(2)RANGE:基于值的范围的窗口框架,它允许你基于列的值来定义窗口的大小。例如,RANGE BETWEEN 0 PRECEDING AND 0 FOLLOWING 会创建一个窗口,其中包含当前行和与当前行具有相同值的行。

 

rows或(range)子句往往来控制窗口边界范围,基本语法:

CURRENT ROW:当前行;

n PRECEDING:往前n行数据;

n FOLLOWING:往后n行数据;

UNBOUNDED PRECEDING: 表示从前面的起点

UNBOUNDED FOLLOWING:表示到后面的终点

 

默认框架:

range BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

 

示例:

rows between 2 preceding and current row   # 取本行和前面两行(总共3行)

rows between unbounded preceding and current row   # 取本行和之前所有的行

rows between current row and unbounded following   # 取本行和之后所有的行

rows between 3 preceding and 1 following   # 从前面三行和下面一行,总共五行

# 当order by后面没有rows between时,窗口规范默认是取本行和之前所有的行

# 当order by和rows between都没有时,窗口规范默认是分组下所有行(rows between unbounded preceding and unbounded following)

 

五、窗口函数示例

创建表

CREATE TABLE `emp` (
  `empno` bigint DEFAULT NULL COMMENT '员工编号',
  `ename` varchar(20) DEFAULT NULL COMMENT '员工姓名',
  `job` varchar(20) DEFAULT NULL COMMENT '职位',
  `mgr` bigint DEFAULT NULL COMMENT '上级经理的员工编号',
  `hiredate` datetime DEFAULT NULL COMMENT '入职日期',
  `sal` bigint DEFAULT NULL COMMENT '基本工资',
  `comm` bigint DEFAULT NULL COMMENT '奖金',
  `deptno` bigint DEFAULT NULL COMMENT '部门编号'
) ;

 

插入数据

INSERT INTO `emp`(`empno`, `ename`, `job`, `mgr`, `hiredate`, `sal`, `comm`, `deptno`) VALUES
 (7369, 'SMITH', 'CLERK', 7902, '1980-01-02 00:00:00', 800, 0, 20),
 (7499, 'ALLEN', 'SALESMAN', 7698, '1981-01-02 00:00:00', 1600, 300, 30),
 (7521, 'WARD', 'SALESMAN', 7698, '1981-01-02 00:00:00', 1250, 500, 30),
 (7566, 'JONES', 'MANAGER', 7839, '1981-01-04 00:00:00', 2975, 0, 20),
 (7654, 'MARTIN', 'SALESMAN', 7698, '1981-01-09 00:00:00', 1250, 1400, 30),
 (7698, 'BLAKE', 'MANAGER', 7839, '1981-01-05 00:00:00', 2850, 0, 30),
 (7782, 'CLARK', 'MANAGER', 7839, '1981-01-06 00:00:00', 2450, 0, 10),
 (7788, 'SCOTT', 'ANALYST', 7566, '1987-01-04 00:00:00', 3000, 0, 20),
 (7839, 'KING', 'PRESIDENT', NULL, '1981-01-01 00:00:00', 5000, 0, 10),
 (7844, 'TURNER', 'SALESMAN', 7698, '1981-01-09 00:00:00', 1500, 0, 30),
 (7876, 'ADAMS', 'CLERK', 7788, '1987-01-05 00:00:00', 1100, 0, 20),
 (7900, 'JAMES', 'CLERK', 7698, '1981-01-02 00:00:00', 950, 0, 30),
 (7902, 'FORD', 'ANALYST', 7566, '1981-01-02 00:00:00', 3000, 0, 20),
 (7934, 'MILLER', 'CLERK', 7782, '1982-01-01 00:00:00', 1300, 0, 10),
 (7948, 'JACCKA', 'CLERK', 7782, '1981-01-04 00:00:00', 5000, 0, 10),
 (7956, 'WELAN', 'CLERK', 7649, '1982-01-07 00:00:00', 2450, 0, 10),
 (7956, 'TEBAGE', 'CLERK', 7748, '1982-01-02 00:00:00', 1300, 0, 10);

 

 

1、排名窗口函数(ROW_NUMVER、RANK、DENSE_RANK)

(1).ROW_NUMVER()

作用: 计算行号。为每一行分配一个唯一的整数,从1开始递增。(编号在组内连续并且唯一)

 

示例:

将所有职工根据部门(deptno)分组(作为开窗列),每个组内根据薪水(sal)做降序排序,获得职工在自己组内的序号:

SELECT
deptno,
ename,
sal,
row_number() over ( PARTITION BY deptno ORDER BY sal DESC ) AS nums
FROM     emp;

返回结果:

 

(2).RANK()

作用:计算排名,排名可能不连续。对每一行进行排名,当遇到相同值时,排名会跳过一些数字。

 

示例:

将所有职工根据部门(deptno)分组(作为开窗列),每个组内根据薪水(sal)做降序排序,获得职工自己组内的序号。

SELECT
       deptno,
       ename,
       sal,
       rank() over ( PARTITION BY deptno ORDER BY sal DESC ) AS nums
FROM     emp;

返回结果:

 

 

(3).DENSE_RANK()

作用:计算排名,排名是连续的。对每一行进行排名,但不会跳过排名数字,即使遇到相同值。

 

示例:

将所有职工根据部门(deptno)分组(作为开窗列),每个组内根据薪水(sal)做降序排序,获得职工自己组内的序号。命令示例如下:

SELECT
       deptno,
       ename,
       sal,
       dense_rank() over ( PARTITION BY deptno ORDER BY sal DESC ) AS nums
FROM     emp;

返回结果:

 

 

2、聚合窗口函数(MAX、MIN、SUM、AVG、COUNT、STDDEV、STDDEV_SAMP)

(1).MAX(列名)

作用:计算窗口中的最大值。

 

示例:

指定部门(deptno)为开窗列,计算薪水(sal)最大值,不排序,返回当前窗口(相同deptno)的最大值。

SELECT
       deptno,
       sal,
       max(sal) over ( PARTITION BY deptno )   dept_max_sal
FROM     emp;

返回结果:

 

 

(2).MIN(列名)

作用:计算窗口中的最小值。

 

示例:

指定部门(deptno)为开窗列,计算薪水(sal)最小值,不排序,返回当前窗口(相同deptno)的最大值。

SELECT
       deptno,
       sal,
       min(sal) over ( PARTITION BY deptno )   dept_min_sal
FROM     emp;

返回结果:

 

 

(3).SUM(列名)

作用:对窗口中的数据求和。

 

指定部门(deptno)为开窗列,计算薪水(sal)汇总值,不排序,返回当前窗口(相同deptno)的累计汇总值。

SELECT
       deptno,
       sal,
       sum(sal) over ( PARTITION BY deptno )  dept_sum_sal
FROM     emp;

返回结果:

 

 

(4).AVG(列名)

作用: 对窗口中的数据求平均值。

 

示例:

指定部门(deptno)为开窗列,计算薪水(sal)平均值,不排序,返回当前窗口(相同deptno)从开始行到最后一行的累计平均值。

SELECT
       deptno,
       sal,
       avg(sal) over ( PARTITION BY deptno )   dept_avg_sal
FROM     emp;

返回结果:

 

 

(5).COUNT(列名)

作用:计算窗口中的记录数。

 

示例:

指定薪水(sal)为开窗列,不排序,返回当前窗口(相同sal)的从开始行到最后一行的累计计数值。

SELECT
       sal,
       count(sal) over ( PARTITION BY sal ) AS  sal_count
FROM     emp;

返回结果:

 

 

(6).STDDEV(列名)

作用:计算总体标准差。使用公式 S = sqrt(sum((x_i - x_bar)^2) / N),其中 N 是样本中的数据点数量。

 

示例:

指定部门(deptno)为开窗列,计算薪水(sal)总体标准差,不排序,返回当前窗口(相同deptno)的累计总体标准差。

SELECT
       deptno,
       sal,
       stddev(sal) over ( PARTITION BY deptno )   stddev_sal
FROM     emp;

返回结果:

 

 

(7).STDDEV_SAMP(列名)

作用:计算样本标准差。使用公式 S = sqrt(sum((x_i - x_bar)^2) / (N - 1)),其中 N 是样本中的数据点数量。

 

示例:

指定部门(deptno)为开窗列,计算薪水(sal)样本标准差,不排序,返回当前窗口(相同deptno)的累计样本标准差。

SELECT
       deptno,
       sal,
       stddev_samp(sal) over ( PARTITION BY deptno )    stddev_samp_sal
FROM    emp;

返回结果:

 

 

3、偏移(前后取值)窗口函数(LAG、LEAD)

(1).LAG(列名,N)

作用:取当前行往前(朝分区头部方向)第N行数据的值。

 

示例:

将所有职工根据部门(deptno)分组(作为开窗列),每位员工的薪水(sal)做偏移。

SELECT
       deptno,
       ename,
       sal,
       lag( sal, 1 ) over ( PARTITION BY deptno ORDER BY sal ) AS sal_new
FROM     emp;

返回结果:

 

 

(2).LEAD(列名,N)

作用:取当前行往后(朝分区尾部方向)第N行数据的值。

 

示例:

将所有职工根据部门(deptno)分组(作为开窗列),每位员工的薪水(sal)做偏移。

SELECT
       deptno,
       ename,
       sal,
       lead( sal, 1 ) over ( PARTITION BY deptno ORDER BY sal ) AS sal_new
FROM     emp;

返回结果:

 

 

4、首尾取值窗口函数(FIRST_VALUE、LAST_VALUE、NTH_VALUE)

(1).FIRST_VALUE(列名)

作用:取窗口内第一行的值。

 

示例:

将所有职工根据部门分组,返回每组中的第一行数据。

# 不指定ORDER BY 

SELECT
       deptno,
       ename,
       sal,
       first_value( sal ) over ( PARTITION BY deptno ) AS first_sal
FROM     emp;

返回结果:

 

 

# 指定order by:

SELECT
       deptno,
       ename,
       sal,
       first_value( sal ) over ( PARTITION BY deptno ORDER BY sal DESC ) AS first_sal 
FROM     emp;

返回结果:

 

 

(2).LAST_VALUE(列名)

作用:取窗口内最后一行的值。

 

示例:

将所有职工根据部门分组,返回每组中的最后一行数据。

# 不指定order by,当前窗口为第一行到最后一行的范围,返回当前窗口的最后一行的值。

SELECT
       deptno,
       ename,
       sal,
       last_value( sal ) over ( PARTITION BY deptno ) AS last_sal 
FROM     emp;

返回结果:

 

 

# 指定order by,当前窗口为第一行到当前行的范围。返回当前窗口的当前行的值。

SELECT
       deptno,
       ename,
       sal,
       last_value( sal ) over ( PARTITION BY deptno ORDER BY sal DESC ) AS last_sal
FROM     emp;

返回结果:

 

 

(3).NTH_VALUE(列名,N)

作用:取窗口内第N行的值。

 

示例:

将所有职工根据部门分组,返回每组中的第6行数据。

# 不指定order by,当前窗口为第一行到最后一行的范围,返回当前窗口第6行的值。

SELECT
    deptno,
    ename,
    sal,
    nth_value( sal, 6 ) over ( PARTITION BY deptno ) AS nth_6_sal  
FROM    emp;

返回结果:

 

# 指定order by,当前窗口为第一行到当前行的范围,返回当前窗口第6行的值。

SELECT
       deptno,
       ename,
       sal,
       nth_value( sal, 6 ) over ( PARTITION BY deptno ORDER BY sal ) AS nth_6_sal 
FROM     emp;

返回结果:

 

 

5、分布窗口函数(PERCENT_RANK、CUME_DIST、NTILE)

(1).PERCENT_RANK()

作用:计算行的百分比排名。输出百分比格式。

返回值等于“(rank - 1) / (partition_row_count - 1)”,其中:rank为该行数据的RANK窗口函数的返回结果,partition_row_count为该行数据所属分区的数据行数。

 

示例:

计算员工薪水在组内的百分比排名。

SELECT
       deptno,
       ename,
       sal,
       percent_rank() over ( PARTITION BY deptno ORDER BY sal DESC ) AS sal_new
FROM     emp;

返回结果:

 

 

(2).CUME_DIST()

作用:计算行相对于所有其他行的累积分布。

返回值等于row_number_of_last_peer / partition_row_count,其中:row_number_of_last_peer指当前行所属GROUP的最后一行数据的ROW_NUMBER窗口函数返回值,partition_row_count为该行数据所属分区的数据行数。

 

示例:

将所有职工根据部门(deptno)分组(作为开窗列),计算薪水(sal)在同一组内的前百分之几。

SELECT
       deptno,
       ename,
       sal,
       concat( round( cume_dist() over ( PARTITION BY deptno ORDER BY sal DESC )* 100, 2 ), '%' ) AS cume_dist_pect
FROM     emp;

返回结果:

 

 

(3).NTILE(N)

作用:将数据顺序切分成N等份,返回数据所在等份的编号(从1到N)。

用于将分区中的数据按照顺序切分成N等份,并返回数据所在等份的编号。如果分区中的数据不能被均匀地切分成N等份时,最前面的等份(编号较小的)会优先多分配1条数据。

 

示例:

将所有职工根据部门按薪水(sal)从高到低切分为3组,并获得职工自己所在组的序号。

SELECT
       deptno,
       ename,
       sal,
       ntile( 3 ) over ( PARTITION BY deptno ORDER BY sal DESC ) AS nt3
FROM     emp;

返回结果:

 

 

posted @ 2025-04-21 17:36  业余砖家  阅读(1502)  评论(0)    收藏  举报