一、开窗函数简介
1、官方文档地址:
https://cwiki.apache.org/conf...
oracle,sqlserver都提供了窗口函数,但是在mysql5.5和5.6都没有提供窗口函数!
2、简介
窗口函数: 窗口+函数
窗口: 函数运行时计算的数据集的范围
函数: 运行的函数!
窗口函数和分组有什么区别?
①如果是分组操作,select后只能写分组后的字段
②如果是窗口函数,窗口函数是在指定的窗口内,对每条记录都执行一次函数
③如果是分组操作,有去重效果,而partition不去重!
3、相关函数说明
格式:函数over( partition by 字段 ,order by 字段 window_clause ),窗口的大小可以通过windows_clause来指定:
- LEAD:往后第n行数据
LEAD (scalar_expression [,offset] [,default]): 返回当前行以下N行的指定列的列值!如果找不到,就采用默认值
- LAG:往前第n行数据
LAG (scalar_expression [,offset] [,default]): 返回当前行以上N行的指定列的列值!如果找不到,就采用默认值
- FIRST_VALUE:
FIRST_VALUE(列名,[false(默认)]): 返回当前窗口指定列的第一个值,第二个参数如果为true,代表加入第一个值为null,跳过空值,继续寻找! - LAST_VALUE:
LAST_VALUE(列名,[false(默认)]): 返回当前窗口指定列的最后一个值,第二个参数如果为true,代表加入第一个值为null,跳过空值,继续寻找!
- 统计类的函数(一般都需要结合over使用): min,max,avg,sum,count
- 排名分析:RANK、ROW_NUMBER、DENSE_RANK、CUME_DIST、PERCENT_RANK
- NTILE
把有序分区中的行分发到指定数据的组中,各个组有编号,编号从1开始,对于每一行,NTILE返回此行所属的组的编号。注意:n必须为int类型。
注意:不是所有的函数在运行都是可以通过改变窗口的大小,来控制计算的数据集的范围!
所有的排名函数和LAG,LEAD,支持使用over(),但是在over()中不能定义 window_clause
(rows | range) between (unbounded | [num]) preceding and ([num] preceding | current row | (unbounded | [num]) following)
(rows | range) between current row and (current row | (unbounded | [num]) following)
(rows | range) between [num] following and (unbounded | [num]) following
特殊情况: ①在over()中既没有出现windows_clause,也没有出现order by,窗口默认为rows between UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING
②在over()中(没有出现windows_clause),指定了order by,窗口默认为rows between UNBOUNDED PRECEDING and CURRENT ROW
CURRENT ROW:当前行
n PRECEDING:往前n行数据
n FOLLOWING:往后n行数据
UNBOUNDED:起点,UNBOUNDED PRECEDING 表示从前面的起点, UNBOUNDED FOLLOWING表示到后面的终点
4、排名函数
注意:排名函数可以跟Over(),但是不能定义window_clause.
在计算名次前,需要先排序!
- RANK: 允许并列,一旦有并列跳号!
- ROW_NUMBER: 行号! 连续的,每个号之间差1!
- DENSE_RANK: 允许并列,一旦有并列不跳号!
- CUME_DIST: 从排序后的第一行到当前值之间数据 占整个数据集的百分比!
- PERCENT_RANK: rank-1/ 总数据量-1
- NTILE(x): 将数据集均分到X个组中,返回每条记录所在的组号
select *,rank() over(order by score) ranknum,
ROW_NUMBER() over(order by score) rnnum,
DENSE_RANK() over(order by score) drnum,
CUME_DIST() over(order by score) cdnum,
PERCENT_RANK() over(order by score) prnum
from score
name | subject | score | ranknum | rnnum | drnum | cdnum | prnum |
---|---|---|---|---|---|---|---|
大海 | 数学 | 56 | 1 | 1 | 1 | 0.083 | 0.0 |
宋宋 | 语文 | 64 | 2 | 2 | 2 | 0.166 | 0.0909 |
婷婷 | 语文 | 65 | 3 | 3 | 3 | 0.25 | 0.181 |
孙悟空 | 英语 | 68 | 4 | 4 | 4 | 0.33 | 0.272 |
婷婷 | 英语 | 78 | 5 | 5 | 5 | 0.416 | 0.363 |
宋宋 | 英语 | 84 | 6 | 6 | 6 | 0.583 | 0.454 |
大海 | 英语 | 84 | 6 | 7 | 6 | 0.583 | 0.454 |
婷婷 | 数学 | 85 | 8 | 8 | 7 | 0.666 | 0.636 |
宋宋 | 数学 | 86 | 9 | 9 | 8 | 0.75 | 0.727 |
孙悟空 | 语文 | 87 | 10 | 10 | 9 | 0.833 | 0.818 |
大海 | 语文 | 94 | 11 | 11 | 10 | 0.916 | 0.909 |
孙悟空 | 数学 | 95 | 12 | 12 | 11 | 1.0 | 1.0 |
二、示例
create table score(
name string,
subject string,
score int)
row format delimited fields terminated by "t";
load data local inpath '/opt/module/datas/score.txt' into table score;
-- 01 按照科目进行排名
-- 注意,就是新增了一列,该列展示排名信息,体会与group by 的区别
select *,rank() over(partition by subject order by score desc)
from score
-- 02 求每个学生的总分和总分排名,输出4条记录
-- 注意,如果只要总分,不要其他清单明细,那么使用group by 求和
select name,sumscore ,rank() over (order by sumscore desc) from
(select name,sum(score) sumscore from score group by name ) tmp
-- 03 求每个学生的成绩明细及给每个学生的总分和总分排名
-- 与上一个的区别,要每个学生的明细+排名,那么只能用窗口函数
select * ,DENSE_RANK() over (order by tmp.sumscore desc ) from
(select *,sum(score) over (partition by name) sumscore from score ) tmp
-- 只查询每个科目的成绩的前2名
-- 取清单+前多少名,可以用 子查询+rank
select * ,rn from
(select * ,rank() over (partition by subject order by score desc ) rn from score ) tmp where rn <=2
-- 查询学生成绩明细,并显示当前科目最高分
--如果只查一个max值,那么也可以用窗口函数,max or first
select *,max(score) over(partition by subject)
from score
或
select *,FIRST_VALUE(score) over(partition by subject order by score desc)
from score
--查询学生成绩,并显示当前科目最低分
select *,min(score) over(partition by subject)
from score
或
select *,FIRST_VALUE(score) over(partition by subject order by score )
from score
--(2)查询顾客的购买明细及月购买总额
-- 注意和下面一个的区别,没有order by ,体会窗口的概念,sum 是顶部到current ,这个表示所有到区间
select name,orderdate,cost,sum(cost) over(partition by name,substring(orderdate,1,7) )
from business
--(3)查询顾客的购买明细要将cost按照日期进行累加
-- 加了order by,窗口的概念
select name,orderdate,cost,sum(cost) over(partition by name order by orderdate )
from business
--(4)查询顾客的购买明细及顾客上次的购买时间
select name,orderdate,cost,lag(orderdate,1,'无数据') over(partition by name order by orderdate )
from business
(5) 查询顾客的购买明细及顾客下次的购买时间
select name,orderdate,cost,lead(orderdate,1,'无数据') over(partition by name order by orderdate )
from business
(6) 查询顾客的购买明细及顾客本月第一次购买的时间
select name,orderdate,cost,FIRST_VALUE(orderdate,true) over(partition by name,substring(orderdate,1,7) order by orderdate )
from business
(7) 查询顾客的购买明细及顾客本月最后一次购买的时间
select name,orderdate,cost,LAST_VALUE(orderdate,true) over(partition by name,substring(orderdate,1,7) order by orderdate rows between CURRENT row and UNBOUNDED FOLLOWING)
from business
(8)查询顾客的购买明细及顾客最近三次cost花费
最近三次: 当前和之前两次 或 当前+前一次+后一次
当前和之前两次:
select name,orderdate,cost,sum(cost) over(partition by name order by orderdate rows between 2 PRECEDING and CURRENT row)
from business
当前+前一次+后一次:
select name,orderdate,cost,sum(cost) over(partition by name order by orderdate rows between 1 PRECEDING and 1 FOLLOWING)
from business
或
select name,orderdate,cost,cost+
lag(cost,1,0) over(partition by name order by orderdate )+
lead(cost,1,0) over(partition by name order by orderdate )
from business
(9) 查询前20%时间的订单信息
精确算法:
select *
from
(select name,orderdate,cost,cume_dist() over(order by orderdate ) cdnum
from business) tmp
where cdnum<=0.2
不精确计算:
select *
from
(select name,orderdate,cost,ntile(5) over(order by orderdate ) cdnum
from business) tmp
where cdnum=1