UDAF简介

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

UDAF的误区

我们可能下意识的认为UDAF是需要和group by一起使用的,实际上UDAF可以跟group by一起使用,也可以不跟group by一起使用,这个其实比较好理解,联想到mysql中的max、min等函数,可以:

select max(foo) from foobar group by bar;

表示根据bar字段分组,然后求每个分组的最大值,这时候的分组有很多个,使用这个函数对每个分组进行处理,也可以:

select max(foo) from foobar group by bar;

这种情况可以将整张表看做是一个分组,然后在这个分组(实际上就是一整张表)中求最大值。所以聚合函数实际上是对分组做处理,而不关心分组中记录的具体数量。

UDAF使用

UDAF 的使用方法有这两种

  • 继承UserDefinedAggregateFunction
  • 继承Aggregator

下面介绍两种UDAF的实现

方法一:继承UserDefinedAggregateFunction

使用UserDefinedAggregateFunction的套路:

1. 自定义类继承UserDefinedAggregateFunction,对每个阶段方法做实现

2. 在spark中注册UDAF,为其绑定一个名字

3. 然后就可以在sql语句中使用上面绑定的名字调用

下面写一个计算平均值的UDAF例子,首先定义一个类继承UserDefinedAggregateFunction:

package cc11001100.spark.sql.udaf

import org.apache.spark.sql.Row
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types._ object AverageUserDefinedAggregateFunction extends UserDefinedAggregateFunction { // 聚合函数的输入数据结构
override def inputSchema: StructType = StructType(StructField("input", LongType) :: Nil) // 缓存区数据结构
override def bufferSchema: StructType = StructType(StructField("sum", LongType) :: StructField("count", LongType) :: Nil) // 聚合函数返回值数据结构
override def dataType: DataType = DoubleType // 聚合函数是否是幂等的,即相同输入是否总是能得到相同输出
override def deterministic: Boolean = true // 初始化缓冲区
override def initialize(buffer: MutableAggregationBuffer): Unit = {
buffer() = 0L
buffer() = 0L
} // 给聚合函数传入一条新数据进行处理
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
if (input.isNullAt()) return
buffer() = buffer.getLong() + input.getLong()
buffer() = buffer.getLong() +
} // 合并聚合函数缓冲区
override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
buffer1() = buffer1.getLong() + buffer2.getLong()
buffer1() = buffer1.getLong() + buffer2.getLong()
} // 计算最终结果
override def evaluate(buffer: Row): Any = buffer.getLong().toDouble / buffer.getLong() }

然后注册并使用它:

package cc11001100.spark.sql.udaf

import org.apache.spark.sql.SparkSession

object SparkSqlUDAFDemo_001 {

  def main(args: Array[String]): Unit = {

    val spark = SparkSession.builder().master("local[*]").appName("SparkStudy").getOrCreate()
spark.read.json("data/user").createOrReplaceTempView("v_user")
spark.udf.register("u_avg", AverageUserDefinedAggregateFunction)
// 将整张表看做是一个分组对求所有人的平均年龄
spark.sql("select count(1) as count, u_avg(age) as avg_age from v_user").show()
// 按照性别分组求平均年龄
spark.sql("select sex, count(1) as count, u_avg(age) as avg_age from v_user group by sex").show() } }

结果

//使用到的数据集
{"id": , "name": "foo", "sex": "man", "age": }
{"id": , "name": "bar", "sex": "man", "age": }
{"id": , "name": "baz", "sex": "man", "age": }
{"id": , "name": "foo1", "sex": "woman", "age": }
{"id": , "name": "bar2", "sex": "woman", "age": }
{"id": , "name": "baz3", "sex": "woman", "age": } //运行结果
+-----+--------+
| count|avg_age|
+-----+--------+
| 6 | 19.6666|
+-----+--------+
+-----+--------+---------+
| sex | count | avg_age |
+-----+--------+---------+
| man| 19.6666|20.666666| |woman| 19.6666|20.666666| +-----+--------+---------+

方法二:继承Aggregator

还有另一种方式就是继承Aggregator这个类,优点是可以带类型:

package cc11001100.spark.sql.udaf

import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql.{Encoder, Encoders} /**
* 计算平均值
*
*/
object AverageAggregator extends Aggregator[User, Average, Double] { // 初始化buffer
override def zero: Average = Average(0L, 0L) // 处理一条新的记录
override def reduce(b: Average, a: User): Average = {
b.sum += a.age
b.count += 1L
b
} // 合并聚合buffer
override def merge(b1: Average, b2: Average): Average = {
b1.sum += b2.sum
b1.count += b2.count
b1
} // 减少中间数据传输
override def finish(reduction: Average): Double = reduction.sum.toDouble / reduction.count override def bufferEncoder: Encoder[Average] = Encoders.product // 最终输出结果的类型
override def outputEncoder: Encoder[Double] = Encoders.scalaDouble } /**
* 计算平均值过程中使用的Buffer
*
* @param sum
* @param count
*/
case class Average(var sum: Long, var count: Long) {
} case class User(id: Long, name: String, sex: String, age: Long) {
}

调用:

package cc11001100.spark.sql.udaf

import org.apache.spark.sql.SparkSession

object AverageAggregatorDemo_001 {

  def main(args: Array[String]): Unit = {

    val spark = SparkSession.builder().master("local[*]").appName("SparkStudy").getOrCreate()
import spark.implicits._
val user = spark.read.json("data/user").as[User]
user.select(AverageAggregator.toColumn.name("avg")).show() } }
//运行结果
+--------+
| avg |
+--------+
| 19.6666|
+--------+

 

spark自定义函数之——UDAF使用详解及代码示例的更多相关文章

  1. spark自定义函数之——UDF使用详解及代码示例

    前言 本文介绍如何在Spark Sql和DataFrame中使用UDF,如何利用UDF给一个表或者一个DataFrame根据需求添加几列,并给出了旧版(Spark1.x)和新版(Spark2.x)完整 ...

  2. laravel 框架配置404等异常页面的方法详解(代码示例)

    本篇文章给大家带来的内容是关于laravel 框架配置404等异常页面的方法详解(代码示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 在Laravel中所有的异常都由Handl ...

  3. php自定义函数call_user_func和call_user_func_array详解

    看UCenter的时候有一个函数call_user_func,百思不得其解,因为我以为是自己定义的函数,结果到处都找不到,后来百度了一下才知道call_user_func是内置函 call_user_ ...

  4. c/c++中define用法详解及代码示例

    https://blog.csdn.net/u012611878/article/details/52534622   版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog. ...

  5. PCA 降维算法详解 以及代码示例

    转载地址:http://blog.csdn.net/watkinsong/article/details/38536463 1. 前言 PCA : principal component analys ...

  6. Hive 自定义函数 UDF UDAF UDTF

    1.UDF:用户定义(普通)函数,只对单行数值产生作用: 继承UDF类,添加方法 evaluate() /** * @function 自定义UDF统计最小值 * @author John * */ ...

  7. JS函数动作分层结构详解及Document.getElementById 释义 js及cs数据类型区别 事件 函数 变量 script标签 var function

    html +css 静态页面 js     动态 交互   原理: js就是修改样式, 比如弹出一个对话框. 弹出的过程就是这个框由disable 变成display:enable. 又或者当鼠标指向 ...

  8. Wordpress菜单函数wp_nav_menu各参数详解及示例

    Wordpress菜单函数wp_nav_menu各参数详解及示例   注册菜单 首先要注册菜单,将以下函数添加至function.php函数里   register_nav_menus(array( ...

  9. 大数据学习day29-----spark09-------1. 练习: 统计店铺按月份的销售额和累计到该月的总销售额(SQL, DSL,RDD) 2. 分组topN的实现(row_number(), rank(), dense_rank()方法的区别)3. spark自定义函数-UDF

    1. 练习 数据: (1)需求1:统计有过连续3天以上销售的店铺有哪些,并且计算出连续三天以上的销售额 第一步:将每天的金额求和(同一天可能会有多个订单) SELECT sid,dt,SUM(mone ...

随机推荐

  1. FS获取KERNEL32基址的三种方法

    FS寄存器指向当前活动线程的TEB结构(线程结构) 偏移  说明 000  指向SEH链指针 004  线程堆栈顶部 008  线程堆栈底部 00C  SubSystemTib 010  FiberD ...

  2. Linux环境进程间通信----系统 V 消息队列(二)

    一.消息队列是一条由消息连接而成的链表,它保存在内核中,通过消息队列的引用标示符来访问. 二.消息队列不同于管道,通信的两个进程可以是完全无关的进程,它们之间不需要约定同步的方法.只要消息队列存在并且 ...

  3. go routine

    无缓冲 channel 是在多个 goroutine 之间同步很棒的工具. 当在 Go 中用 ch := make ( chan bool ) 创建 chennel 时,bool 型的 无缓冲 cha ...

  4. 使用FTPClient实现文件上传服务器

    import ch.qos.logback.classic.Logger; import org.apache.commons.net.ftp.*; import org.slf4j.LoggerFa ...

  5. Openstack组件实现原理 — Glance架构(V1/V2)

    目录 目录 Glance 安装列表 Glance Image service Image service 的组件 Glance-Api Glance-Registry Glance-db Image ...

  6. TLS/SSL 协议 - ServerHello

    ServerHello ServerHello消息的意义是将服务器选择的连接参数传送回客户端.这个消息的结构与ClientHello类似,只是每个字段只包含一个选项. 服务器无需支持客户端支持的最佳版 ...

  7. 如何从ST官网下载STM32标准库

    Frm:https://blog.csdn.net/k1ang/article/details/79645044

  8. JSON数组对象和JSON字符串的转化,map和JSON对象之间的转化

    这种用法包括前端和后端: 前端: 1. 转化为JSON对象方便操作 var jsonObj = JSON.parse(str); 得到的是一个json数组对象,可以通过 for (var p in j ...

  9. 框架_mybatis2使用注解

    在dao中使用注解: package cn.dao; import cn.mepu.User; import org.apache.ibatis.annotations.Select; import ...

  10. Mongodb安装遇到的问题

    今天我第一次接触MongoDB,在安装过程中,因为对这个东西不了解,报错了我也不知道是哪里的错,还在网上查了半天资料,后来我懂了,感觉上午就是在浪费时间,是我没睡醒,没睡醒.. 在安装上MongoDB ...