Spark SQL操作之-窗口函数篇-中
环境说明
1. JDK 1.8
2. Spark 2.1
窗口函数是什么
窗口函数,顾名思义,这里存在一个窗口的概念。也就是指表内数据参与到函数计算的一个区间。这里说的计算区间,我理解是有两个意思。第一是看是否需要按指定的列来对数据进行分区。第二是看分区确定后是否还指定了对分区数据的进一步的限定。包括rows区间和range区间两种限定,后面会一一举例说明。具体对应到SQL语句中,就是over语句的部分。
窗口定义的相关类有两个:
- org.apache.spark.sql.expressions.Window
- org.apache.spark.sql.expressions.WindowSpec
Spark的文档里关于Window类的说明很奇怪,类的函数一个都没提到。实际上写代码时需要import的就是Window类,查看了Spark源码里这个类文件,这些函数确实都是有的。这是一个普通的工具类,用来定义窗口,相关函数如下:
函数原型 | 功能说明 |
---|---|
partitionBy(colName: String, colNames: String*): WindowSpec | 按给定的列名对整个数据分区 |
partitionBy(cols: Column*): WindowSpec | 同上,只是参数类型的差异 |
orderBy(colName: String, colNames: String*): WindowSpec | 分区内的数据按指定列排序 |
orderBy(cols: Column*): WindowSpec | 同上 |
rowsBetween(start: Long, end: Long): WindowSpec | 指定区间内的进一步区间限制 |
rangeBetween(start: Long, end: Long): WindowSpec | 指定区间内的进一步区间限制 |
rowsBetween函数和rangeBetween函数的参数都是Long型,就是靠start和end这两个值来约束区间。看Spark的文档里,把这个区间叫做frame。要注意的是,窗口函数是针对每一行来处理的,所以这里的start和end都是相对于当前行这个概念。
窗口函数和其他函数的区别
- 普通函数是作用于每一行记录,对每一行记录中的列来计算出一个返回值作为新列,表的记录数不改变。
- 聚合函数是作用于一组记录,对这一组记录中的列计算出一个值,做聚合后总的记录数通常会减少。
- 窗口函数则是对于每一行记录,都要根据指定的多行记录来计算出一个值 ,最后表的记录数不变。
窗口函数列表
文档中functions类里单列出的窗口函数不多,看了一下Spark v2.1.0里面提供的总共是8个,主要是排序相关的函数。实际上,到最新版的v2.4.3也一样。一般的聚合函数,像是first, last, count, sum之类的也都是可以用于窗口计算的。
函数名 | 函数功能 | 函数原型 |
cume_dist | 计算窗口范围内的累积分布 | cume_dist(): [Column] |
dense_rank | 返回窗口内的排名。和下面的rank的区别是不会跳过排名的序号。比如有两个并列第一,那么第三个的rank是2,而rank的话,是3,跳过了2 | dense_rand(): [Column] |
rank | 返回窗口内的排名,主要是注意与dense_rank的区别 | rank(): [Column] |
row_number | 返回窗口内从1开始的序号 | row_number(): [Column] |
percent_rank | 返回窗口内的相对排名 | percent_rank(): [Column] |
lag | 返回当前行之前某几行的值,不同的原型有些细节不一样,比如null值怎么处理之类的。 | lag(e: [Column], offset: Int, defaultValue: Any): [Column] |
lag(columnName: String, offset: Int, defaultValue: Any): [Column] | ||
lag(columnName: String, offset: Int): [Column] | ||
lag(e: [Column], offset: Int): [Column] | ||
lead | 返回当前行之后的某几行的值,不同的原型有些细节不一样,比如null值怎么处理之类的。跟lag的处理一样,只是方向是反的 | lead(e: [Column], offset: Int, defaultValue: Any): [Column] |
lead(columnName: String, offset: Int, defaultValue: Any): [Column] | ||
lead(e: [Column], offset: Int): [Column] | ||
lead(columnName: String, offset: Int): [Column] | ||
ntile | ntile是个将窗口内的记录尽量均匀分组的函数,返回分组后记录对应的组id。 | ntile(n: Int): [Column] |
示例详解
概念性的东西介绍完了,再来结合数据看一下就应该能彻底弄清楚窗口函数到底是怎么回事了。
scala> val df=Seq((0,1,100),(1,2,50),(2,3,40),
| (3,1,70),(4,2,60),(5,3,40),
| (6,1,30),(7,2,50),(8,3,100),
| (9,1,51),(10,2,72), (11,3,35),
| (12,1,45),(13,2,25)
| ).t