【计算引擎】spark笔记-UDF&&UDAF


使用实例见大数据处理的一些方法及代码

1. UDF、UDAF、UDTF

1.1 UDF

UDF(User-defined functions)用户自定义函数,简单说就是输入一行输出一行的自定义算子。
是大多数 SQL 环境的关键特性,用于扩展系统的内置功能。(一对一)

1.2 UDAF

UDAF
UDAF(User Defined Aggregate Function),即用户定义的聚合函数,聚合函数和普通函数的区别是什么呢,普通函数是接受一行输入产生一个输出,聚合函数是接受一组(一般是多行)输入然后产生一个输出,即将一组的值想办法聚合一下。(多对一)

UDAF可以跟group by一起使用,也可以不跟group by一起使用,这个其实比较好理解,联想到mysql中的max、min等函数,可以:
select max(foo) from foobar group by bar;
表示根据bar字段分组,然后求每个分组的最大值,这时候的分组有很多个,使用这个函数对每个分组进行处理,也可以:
select max(foo) from foobar;
这种情况可以将整张表看做是一个分组,然后在这个分组(实际上就是一整张表)中求最大值。所以聚合函数实际上是对分组做处理,而不关心分组中记录的具体数量。

1.3 UDTF

UDTF
UDTF(User-Defined Table-Generating Functions),用户自定义生成函数。它就是输入一行输出多行的自定义算子,可输出多行多列,又被称为 “表生成函数”。(一对多)

2. 应用UDF函数

  1. 通过spark.udf.register(name,func)来注册一个UDF函数,name是UDF调用时的标识符,fun是一个函数,用于处理字段。
  2. 需要将一个DF或者DS注册为一个临时表。
  3. 通过spark.sql去运行一个SQL语句,在SQL语句中可以通过 name(列名) 方式来应用UDF函数。

3. UDAF 用户自定义聚合函数

3.1 弱类型用户自定义聚合函数

1. 新建一个Class 继承UserDefinedAggregateFunction  ,然后复写方法:
    
    //聚合函数需要输入参数的数据类型
    override def inputSchema: StructType = ???

    //可以理解为保存聚合函数业务逻辑数据的一个数据结构
	override def bufferSchema: StructType = ???

    // 返回值的数据类型
	override def dataType: DataType = ???

    // 对于相同的输入一直有相同的输出
	override def deterministic: Boolean = true

     //用于初始化你的数据结构
	override def initialize(buffer: MutableAggregationBuffer): Unit = ???

     //用于同分区内Row对聚合函数的更新操作
	override def update(buffer: MutableAggregationBuffer, input: Row): Unit = ???

    //用于不同分区对聚合结果的聚合。
	override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = ???

    //计算最终结果
	override def evaluate(buffer: Row): Any = ???
	
	
2. 你需要通过spark.udf.resigter去注册你的UDAF函数。
3. 需要通过spark.sql去运行你的SQL语句,可以通过 select UDAF(列名) 来应用你的用户自定义聚合函数。
  • 实例:实现求平均工资
/*
定义类继承UserDefinedAggregateFunction,并重写其中方法
*/
class MyAveragUDAF extends UserDefinedAggregateFunction {

  // 聚合函数输入参数的数据类型
  def inputSchema: StructType = StructType(Array(StructField("age",IntegerType)))

  // 聚合函数缓冲区中值的数据类型(age,count)
  def bufferSchema: StructType = {
    StructType(Array(StructField("sum",LongType),StructField("count",LongType)))
  }

  // 函数返回值的数据类型
  def dataType: DataType = DoubleType

  // 稳定性:对于相同的输入是否一直返回相同的输出。
  def deterministic: Boolean = true

  // 函数缓冲区初始化
  def initialize(buffer: MutableAggregationBuffer): Unit = {
    // 存年龄的总和
    buffer(0) = 0L
    // 存年龄的个数
    buffer(1) = 0L
  }

  // 更新缓冲区中的数据
  def update(buffer: MutableAggregationBuffer,input: Row): Unit = {
    if (!input.isNullAt(0)) {
      buffer(0) = buffer.getLong(0) + input.getInt(0)
      buffer(1) = buffer.getLong(1) + 1
    }
  }

  // 合并缓冲区
  def merge(buffer1: MutableAggregationBuffer,buffer2: Row): Unit = {
    buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0)
    buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
  }

  // 计算最终结果
  def evaluate(buffer: Row): Double = buffer.getLong(0).toDouble / buffer.getLong(1)
}

3.2 强类型的用户自定义聚合函数

1. 新建一个class,继承Aggregator[Employee, Average, Double],其中Employee是在应用聚合函数的时候传入的对象,Average是聚合函数在运行的时候内部需要的数据结构,Double是聚合函数最终需要输出的类型。这些可以根据自己的业务需求去调整。复写想对应的方法:
    
       //用于定义一个聚合函数内部需要的数据结构
	override def zero: Average = ???

        //针对每个分区内部每一个输入来更新你的数据结构
	override def reduce(b: Average, a: Employee): Average = ???

       //用于对于不同分区的结构进行聚合
	override def merge(b1: Average, b2: Average): Average = ???

       //计算输出
	override def finish(reduction: Average): Double = ???

       //用于数据结构他的转换
	override def bufferEncoder: Encoder[Average] = ???

       //用于最终结果的转换
	override def outputEncoder: Encoder[Double] = ???


2. 新建一个UDAF实例,通过DF或者DS的DSL风格语法去应用。
  • 实例:实现求平均工资
//输入数据类型
case class User01(username:String,age:Long)
//缓存类型
case class AgeBuffer(var sum:Long,var count:Long)
/**
  * 定义类继承org.apache.spark.sql.expressions.Aggregator
  * 重写类中的方法
  */
class MyAveragUDAF1 extends Aggregator[User01,AgeBuffer,Double]{
  override def zero: AgeBuffer = {
    AgeBuffer(0L,0L)
  }

  override def reduce(b: AgeBuffer, a: User01): AgeBuffer = {
    b.sum = b.sum + a.age
    b.count = b.count + 1
    b
  }

  override def merge(b1: AgeBuffer, b2: AgeBuffer): AgeBuffer = {
    b1.sum = b1.sum + b2.sum
    b1.count = b1.count + b2.count
    b1
  }

  override def finish(buff: AgeBuffer): Double = {
    buff.sum.toDouble/buff.count
  }
  //DataSet默认额编解码器,用于序列化,固定写法
  //自定义类型就是produce   自带类型根据类型选择
  override def bufferEncoder: Encoder[AgeBuffer] = {
    Encoders.product
  }
    override def outputEncoder: Encoder[Double] = {
    Encoders.scalaDouble
  }
}

使用实例见大数据处理的一些方法及代码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孟知之

如果能帮助到你们,可否点个赞?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值