介绍
UDAF,User-Defined Aggregation Functions,用户定义 聚合函数。
应用场景
spark 自带的函数并不能完全满足业务需求,这时就需要我们自定义函数了
特点
many to one,进来多个数据,出去一个结果,是 row 级别操作,如:聚合函数 avg、sum、min
执行过程
实现
spark 3.x 实现
定义类,并继承 Aggregator[-IN, BUF, OUT] 类,然后重写下面方法:
// 初始化buffer,buffer是缓存结果,如:累加结果
def zero: Buff
// 处理一条新的记录,b是缓存结果,a是输入数据
def reduce(b: Buff, a: IN): Buff
// 合并两个buff
def merge(b1: Buff, b2: Buff): Buff
// 最终计算
def finish(reduction: Buff): OUT
// 固定写法,如果缓存是自定义类型,那么赋值 Encoders.product
def bufferEncoder: Encoder[Buff] = Encoders.product
// 最终输出结果的类型
def outputEncoder: Encoder[Double] = Encoders.scalaDouble
案例
定义 UDAF 实现 avg() 求平均数功能
MyAvg 类
该类 继承 Aggregator,实现相应的方法,实现求平均数
package udaf
import org.apache.spark.sql.{Encoder, Encoders}
import org.apache.spark.sql.expressions.Aggregator
/**
* 计算平均值过程中使用的Buffer
*
* @param sum
* @param count
*/
case class Buff(var sum: Long, var count: Long) {
}
/**
* 计算平均值
*
*/
class MyAvg extends Aggregator[Long, Buff, Double] {
// 初始化buffer
override def zero: Buff = Buff(0L, 0L)
// 处理一条新的记录
override def reduce(b: Buff, a: Long): Buff = {
b.sum += a
b.count += 1L
b
}
// 合并聚合buffer
override def merge(b1: Buff, b2: Buff): Buff = {
b1.sum += b2.sum
b1.count += b2.count
b1
}
//
override def finish(reduction: Buff): Double = reduction.sum.toDouble / reduction.count
override def bufferEncoder: Encoder[Buff] = Encoders.product
// 最终输出结果的类型
override def outputEncoder: Encoder[Double] = Encoders.scalaDouble
}
主类
注册刚才的 UDAF 函数,然后在 SQL 中调用
package udaf
import org.apache.spark.SparkConf
import org.apache.spark.sql.{DataFrame, SparkSession, functions}
object Std1 {
def main(args: Array[String]): Unit = {
val sc = new SparkConf().setMaster("local[*]").setAppName("std")
val spark = SparkSession.builder().config(sc).getOrCreate()
val df: DataFrame = spark.read
.option("header", "false") //默认为false,表示第一行不是表头
.option("sep", ",") //列分隔符
.csv("cdz.txt") //读取文件
.toDF("id","ele_num","time","start_time","end_time")
df.printSchema()
//创建临时视图(只能查询不能修改)
df.createOrReplaceTempView("cdz")
// 自定义函数
spark.udf.register("myavg",functions.udaf(new MyAvg()))
// val sql = "select myavg(ele_num) as avg,avg(ele_num) as avg2 from cdz"
val sql = "select id,myavg(ele_num) as avg,avg(ele_num) as avg2 from cdz group by id"
val res: DataFrame = spark.sql(sql)
res.show()
spark.close()
}
}
参考:
https://www.cnblogs.com/cc11001100/p/9471859.html
https://zhuanlan.zhihu.com/p/380127118
