文章目录

  • 3.9 窗口函数
  • 3.9.1 定义与作用
  • 3.9.2 语法
  • 01) 语法:
  • 02) 滑动窗口
  • 03) 窗口表初始情况
  • 3.9.2 排序窗口
  • 3.9.3 分布窗口
  • 3.9.4 取前取后窗口
  • 3.9.5 其他
  • 3.9.6 滑动窗口


3.9 窗口函数

3.9.1 定义与作用

  • 所谓窗口,就是在每一行数的在执行的时候,旁边都会有一个相应的窗口,窗口中的数据是原表的全部数据,可以通过指定分组形式和排序形式是窗口中的数据呈现不同的顺序(见下图)。
    因此窗口函数可以抽象为当前表的另一个形式,既然是表,我们就可以想对普通表一样对窗口表进行操作,唯一不同的是窗口表中的分组为 partition by,排序仍为一样,order by
  • 窗口函数的作用主要体现在,即需要单行查询,又需要多行统计的时候,亦或者说是即需要行内相关,又需要行外相关的时候。
    一般情况下,单行查询和多行统计(聚合)无法一起使用,因为单行查询的结果为1变1,记录数不变,而多行统计为多变1,记录变少无法融合。因此窗口函数的出现就是为了解决这种情况:
    若有窗口的存在,当同时单行查询和多行统计时候,可以现在原表中进行单行查询,然后再在窗口表中进行多行统计,统计结果与单行查询结果合并成一个新的行,这样就实现了单行查询和多行统计的同时进行了。可以看出,每一行查询都需要对窗口函数进行查询。

mysql窗口函数取最新一条数据 mysql5实现窗口函数_mysql窗口函数取最新一条数据

3.9.2 语法

01) 语法:
select col1, 
	windows_func() over([partiton by ...] [ order by ...] [rows\ranges ... ])
  • windows_func():为窗口函数,主要有排序函数,分布函数,取前取后等函数,还有聚合函数(多行统计函数)
  • partition by:相当于group by,对进入窗口表进行分组。
  • order by:对窗口表进行排序,和普通表一样
  • rows\ranges:滑动窗口。
02) 滑动窗口
  • 为多行统计指定一个区间,这个区间将根据单行查询的进行而进行移动,这就是窗口函数。
    简单的例子:统计每三行的平均值,因此需要指定窗口为3,每次统计时,取当前行与相邻的两行进行计算,每查询完一行就向下移动,重新取当前行和相邻的两行进行计算,以此往复进行,这样就达到了滑动的作用。
  • 滑动窗口由关键字:rows 或 ranges 指定
  • rows [between … and …]:通过指定行数来定义窗口的大小
  • current Row:表示当前行
  • N preceding:前n行,包括当前行
  • N following:后n行
  • unbounded preceding:开头到当前行
  • unbounded following:当前行到结尾
# 演示
select salary, avg(salary) over(rows between 1 preceding and 1 following); 	# 去每三行的平均值
select salary, sum(salary) over(rows 2 prceding )	# 取当前行与前两行的均值
  • ranges [between…and…]:通过指定值的范围来定义窗口的大小,需要在order by后面
  • V precding\following:相差V
  • 演示
# :查询每个员工的工资,以及与他工资相差少于1000的员工的人数
select salary, count(*) over(order by salary range between 1000 preceding and 1000 following)

# 最近7天的值
range interval 7-1 day preceding	
# 前后一天和当天的值
range between interval 1 day preceding and interval 1 day following
03) 窗口表初始情况
  • 注意一点:窗口表的初始情况由传入表决定,通过重新组队排序才成为我们需要的表。
  • 我们知道,关键字的执行顺序为
    from --> join on --> where --> groupby --> having --> select --> order by --> limit n
    窗口函数在select中才被执行,执行前以及经过了where 和groupby 的操作,所以传入窗口函数是经过筛选后的表了,groupby不会对窗口表产生影响

3.9.2 排序窗口

  • row_number():当前行在分组内的序号,就是简单的顺序
  • dense_rank():不间断的组内排序,例如:A A B的排序结果为 1 1 2
  • rand():间断的组内排序,例如:A A B 的排序结果为 1 1 3
  • rand()和dense_rank()是序号排序的序号,因此需要在窗口内指定排序的最对象,也就是order by,否则默认为1
    row_number()不需要指定排序对象也可以获得序号

row_number()演示:

# 组内序号的使用
mysql> select salary, department_id, row_number() over(partition by department_id order by salary) as 组内序号 from employees limit 10;
+----------+---------------+----------+
| salary   | department_id | 组内序号 |
+----------+---------------+----------+
|  7000.00 |          NULL |        1 |
|  4400.00 |            10 |        1 |
|  6000.00 |            20 |        1 |
| 13000.00 |            20 |        2 |

# 
mysql> select salary, department_id, row_number() over(partition by department_id ) as 组内序号 , row_number() over () as 组外序号 from employees limit 10;
+----------+---------------+----------+----------+
| salary   | department_id | 组内序号 | 组外序号 |
+----------+---------------+----------+----------+
|  7000.00 |          NULL |        1 |        1 |
|  4400.00 |            10 |        1 |        2 |
| 13000.00 |            20 |        1 |        3 |
|  6000.00 |            20 |        2 |        4 |

dense_rank() , rank() 演示:获得各个员工在该部门中的工资排名

select department_id, salary, dense_rank() over(partition by department_id order by salary) as 'dense_rank', rank() over(partition by department_id order by salary) as 'rank'
from employees;
#
+---------------+----------+------------+------+
| department_id | salary   | dense_rank | rank |
+---------------+----------+------------+------+
|            50 |  2100.00 |          1 |    1 |
|            50 |  2200.00 |          2 |    2 |
|            50 |  2200.00 |          2 |    2 |
|            50 |  2400.00 |          3 |    4 |
|            50 |  2400.00 |          3 |    4 |
|            50 |  2500.00 |          4 |    6 |		# 可以看序号方式是不一样的
|            50 |  2500.00 |          4 |    6 |
|            50 |  2500.00 |          4 |    6 |
|            50 |  2500.00 |          4 |    6 |

3.9.3 分布窗口

  • percent_rank():累计百分比
  • cume_dist():累计分布函数值,即P(x<x_i)
  • 详细区别看例子
    获得各个员工的工资在该部门的累计百分比,和分布函数值
select department_id, salary, 
	percent_rank() over(partition by department_id order by salary) as 'percent_rank',
    cume_dist() over(partition by department_id order by salary) as 'cume_dist'
from employees limit 20;

+---------------+----------+----------------------+----------------------+
| department_id | salary   | percent_rank         | cume_dist            |
+---------------+----------+----------------------+----------------------+
|          NULL |  7000.00 |                    0 |                    1 |
|            10 |  4400.00 |                    0 |                    1 |	precent_rank()更像是归一化,得到的值是它的排序。
|            20 |  6000.00 |                    0 |                  0.5 |	假设比当前行值小的函数为n,总函数为N,则其返回的值为:
|            20 | 13000.00 |                    1 |                    1 |	(n) / (N - 1)
|            30 |  2500.00 |                    0 |  0.16666666666666666 |	
|            30 |  2600.00 |                  0.2 |   0.3333333333333333 |	cume_dist()求概率分布值
|            30 |  2800.00 |                  0.4 |                  0.5 |	假设小于等于当前行值的行数为n,总行数为N,则其返回值为
|            30 |  2900.00 |                  0.6 |   0.6666666666666666 |	n / N
|            30 |  3100.00 |                  0.8 |   0.8333333333333334 |
|            30 | 11000.00 |                    1 |                    1 |
|            40 |  6500.00 |                    0 |                    1 |
|            50 |  2100.00 |                    0 | 0.022222222222222223 |
|            50 |  2200.00 | 0.022727272727272728 |  0.06666666666666667 |
|            50 |  2200.00 | 0.022727272727272728 |  0.06666666666666667 |
|            50 |  2400.00 |  0.06818181818181818 |   0.1111111111111111 |
|            50 |  2400.00 |  0.06818181818181818 |   0.1111111111111111 |
|            50 |  2500.00 |  0.11363636363636363 |   0.2222222222222222 |
|            50 |  2500.00 |  0.11363636363636363 |   0.2222222222222222 |
|            50 |  2500.00 |  0.11363636363636363 |   0.2222222222222222 |
|            50 |  2500.00 |  0.11363636363636363 |   0.2222222222222222 |
+---------------+----------+----------------------+----------------------+

3.9.4 取前取后窗口

  • lag(列名, [n, [default]]):取前n行的某一列的数,与当前行合并作为新的列。结果可能为(xi+n, xi),n默认为1, default默认为null
  • lead(列名, [n, [default]]):取后n行
  • first_value(列名):取当分组的第一行的某一列的值
  • last_value(列名):当前行
  • nth_value(列名, n):取截止当前行中的第n行

演示:其实这些都大同小异,不都演示了

# 查询员工工资,以及部门内工资离他最近的员工的工资
select department_id, salary, 
	lag(salary, 1, null) over(partition by department_id order by salary desc range between  unbounded preceding and 0.001 preceding) as 'more',  
	lead(salary, 1, null) over(partition by department_id order by salary desc range between 0.001 following  and unbounded following) as 'least'
from employees limit 10;
+---------------+----------+----------+---------+
| department_id | salary   | more     | least   |
+---------------+----------+----------+---------+
|          NULL |  7000.00 |     NULL |    NULL |
|            10 |  4400.00 |     NULL |    NULL |
|            20 | 13000.00 |     NULL | 6000.00 |
|            20 |  6000.00 | 13000.00 |    NULL |
|            30 | 11000.00 |     NULL | 3100.00 |
|            30 |  3100.00 | 11000.00 | 2900.00 |
|            30 |  2900.00 |  3100.00 | 2800.00 |
|            30 |  2800.00 |  2900.00 | 2600.00 |
|            30 |  2600.00 |  2800.00 | 2500.00 |
|            30 |  2500.00 |  2600.00 |    NULL |
+---------------+----------+----------+---------+

3.9.5 其他

  • ntile(n):对分组内数据进行分桶,并返回对应桶的序号
# 将全部工资按顺序进行分桶,个数为10,对各个部门进行分桶,个数为5,并返回各个员工所在的桶
select employee_id, department_id, salary, 
	ntile(10) over(order by salary) as '组外桶',
	ntile(5) over(partition by department_id order by salary) as '组内桶'
from employees 
limit 10;

+-------------+---------------+----------+--------+--------+
| employee_id | department_id | salary   | 组外桶 | 组内桶 |
+-------------+---------------+----------+--------+--------+
|         178 |          NULL |  7000.00 |      6 |      1 |
|         200 |            10 |  4400.00 |      5 |      1 |
|         202 |            20 |  6000.00 |      5 |      1 |
|         201 |            20 | 13000.00 |     10 |      2 |
|         119 |            30 |  2500.00 |      1 |      1 |
|         118 |            30 |  2600.00 |      2 |      1 |
|         117 |            30 |  2800.00 |      2 |      2 |
|         116 |            30 |  2900.00 |      2 |      3 |
|         115 |            30 |  3100.00 |      3 |      4 |
|         114 |            30 | 11000.00 |      9 |      5 |
+-------------+---------------+----------+--------+--------+

3.9.6 滑动窗口

  • 查询与每五个相近工资的平均值
select salary, 
	avg(salary) over(order by salary rows between 2 preceding and 2 following) as 'avg'
from employees limit 10;

+---------+-------------+
| salary  | avg         |
+---------+-------------+
| 2100.00 | 2166.666667 |
| 2200.00 | 2225.000000 |
| 2200.00 | 2260.000000 |
| 2400.00 | 2340.000000 |
| 2400.00 | 2400.000000 |
| 2500.00 | 2460.000000 |
| 2500.00 | 2480.000000 |
| 2500.00 | 2500.000000 |
| 2500.00 | 2500.000000 |
| 2500.00 | 2520.000000 |
+---------+-------------+
10 rows in set (0.00 sec)