今天没什么事,突然想起之前写过的sqark中SQL中的UDAF方法,这个还是挺有意思的,难度比蜂房中UDAF高,其中直接体现了火花的分而治之的细想,所以打算今天的博客在加一个火花SQL的UDF和UDAF编写。
直接进入正题。
1.udf函数的
编写.sqlContext.udf.register(“CTOF”,(degreesCelcius:Double)=>((degreesCelcius * 9.0 / 5.0)+ 32.0))
sqlContext.sql(“SELECT city,CTOF(avgLow)AS avgLowF ,CTOF(avgHigh)AS avgHighF FROM citytemps“)。show()
较为简单不做过多描述。
2.udfa函数的编写。
import org.apache.spark.sql.Row import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction} import org.apache.spark.sql.types.{DataType, StringType, StructField, StructType} /** * @author 李春凯 * 在开始前有必要了解一下 StructField.DataType 中支持的类型 * 类型有NullType, DateType, TimestampType, BinaryType,IntegerType, BooleanType, LongType, DoubleType, FloatType, ShortType, ByteType, StringType * 用法 : 在要使用该函数的地方需要使用sqlContext声明 函数名 才能使用 * e.g sqlContext.udf.register("numsAvg", new MyUDAF) */ class MyUDAF extends UserDefinedAggregateFunction { /** * 指定具体的输入数据的类型 支持多个值输入 * 自段名称随意:Users can choose names to identify the input arguments - 这里可以是“name”,或者其他任意串 * 这里支持的格式有 NullType, DateType, TimestampType, BinaryType, * IntegerType, BooleanType, LongType, DoubleType, FloatType, ShortType, ByteType, StringType * */ override def inputSchema: StructType = StructType(StructField("docid",StringType)::Nil) /** * 在进行聚合操作的时候所要处理的数据的中间结果类型 * 支持多个值进行计算 * 根据具体需求声明参数个数 可以是一个或者是多个 在initialize方法中初始化 */ override def bufferSchema: StructType = StructType(StructField("val",StringType)::Nil) /** * 输出类型的定义,可以是对应类型的Array或者是AraayBuffer * e.g StringType 可以输出 String Array[String] 和 AraayBuffer[String] 但是不能是String[] */ override def dataType: DataType = StringType override def deterministic: Boolean = true /** * 初始化Buffer * * @param buffer 用于接收和存储输入的值 * buffer(0)对应bufferSchema的第一个类型的值 * buffer(1)对应bufferSchema的第二个类型的值 * 以此类推 * buffer(0) 不支持数组,集合和可变集合 * e.g 支持StringType但是不支持Arry[String] ArryBuffer[String] 和 String[] * --- 该类主要用于参考 * --- 该类实现另外多个列的一个字段组合成了一个列的字段 */ override def initialize(buffer: MutableAggregationBuffer): Unit = { buffer(0) = "" } /** * 将同久值与新值合并的地方 * 注意: 这里的新参数【buffer】因为在初始化的时候就转换了所以在这里不用再次转换, * 但是input【inputSchema】是传入的新值类型是IntegerTyper,和inputSchema的bufferSchema的类型不一致 , * 所以需要转换 * @param buffer 旧值 如果之前没有值传入吗,这个值就是初始值 * @param input 新值函数每次接收的值,空值不会传进来 */ override def update(buffer: MutableAggregationBuffer, input: Row): Unit ={ val Str1 = buffer.getAs[String](0) val Str2 = input.getAs[String](0) if(Str1.trim.equals("")){ buffer(0)=buffer.getAs[String](0)+input.getAs[String](0) }else{ buffer(0) =buffer.getAs[String](0)+"||"+input.getAs[String](0) } } /** * 将多个线程中的的内容合并在一个 Buffer1中, * 这个操作类似于mapreduce中的conbiner * 也类似于spark中的conbinerbykey函数的分区合并 * @param buffer1 第一个线程/上一个线程【或者是分区,这里我不是很清楚到底有没有分区的概念 】 * @param buffer2 第二个线程或者说是下一个线程 */ override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = { val str1 = buffer1.getString(0) val str2 = buffer2.getString(0) if(str1.trim.equals("")){ buffer1(0)=str1+str2 }else{ buffer1(0) = str1+"||"+str2 } // buffer1(0) = buffer1.getAs[String](0) +"="+buffer2.getAs[String](0) } /** * 指定返回值的内容 这里的返回类型只支持DataType对应的类型 不支持数组,集合和可变集合 * e.g 支持StringType但是不支持Arry[String] ArryBuffer[String] 和 String[] * @param buffer * @return */ override def evaluate(buffer: Row): Any = { // buffer.getAs[String](0) buffer.getString(0) } }
3.这里我在添加一个多个字段组合成一个字段的UDAF,这个类主要是方便大家对比理解其中的思想。
package com.ql.UDAFUtil import org.apache.spark.sql.Row import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction} import org.apache.spark.sql.types.{DataType, StringType, StructField, StructType} /** * 将多行多个字段组合成一行一个字段 */ class kCollect_Set extends UserDefinedAggregateFunction{ /* * collect_set(CONCAT(\"http://112.124.126.220:8080/slfh/download?path=\",da.filePath,da.fileName,\"&fileName=\",da.sourceFileName * String * |-- fileName: string (nullable = true) * |-- filePath: string (nullable = true) * String * |-- sourceFileName: string (nullable = true) */ override def inputSchema: StructType = StructType( StructField("http",StringType):: StructField("filePath",StringType):: StructField("filePath",StringType):: StructField("symbol",StringType):: StructField("str",StringType):: StructField("sourceFileName",StringType):: Nil) override def bufferSchema: StructType = StructType( StructField("http_buffer",StringType):: Nil) override def dataType: DataType = StringType override def deterministic: Boolean = true override def initialize(buffer: MutableAggregationBuffer): Unit = { buffer(0)="" } override def update(buffer: MutableAggregationBuffer, input: Row): Unit = { val input1 = input.getAs[String](0) val input2 = input.getAs[String](1) val input3 = input.getAs[String](2) val input4 = input.getAs[String](3) val input5 = input.getAs[String](4) val input6 = input.getAs[String](5) var str = "" // collect_set(CONCAT(\"http://112.124.126.220:8080/slfh/download?path=\",da.filePath,da.fileName,\"&fileName=\",da.sourceFileName if(input1!=null){ str = str+input1} if(input2!=null){ str = str+input2 } if(input3!=null){ str = str+input3 } if(input4!=null){ str = str+input4 } if(input5!=null){ str = str+input5 } if(input6!=null){ str = str+input6 } val buf = buffer.getAs[String](0) if(buf.trim.equals("")){ buffer(0)=buf+str }else{ buffer(0)=buf+"||"+str } } override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = { var str1 = buffer1.getString(0) var str2 = buffer2.getString(0) if(str1.trim.equals("")){ buffer1(0)=str1+str2 }else{ buffer1(0)=str1+"||"+str2 } } override def evaluate(buffer: Row): Any = { buffer.getString(0) } }
4.总结spark sql中的udaf类的操作稍微复杂一点,但是并不是不能理解的,在我的第一个中中【myudaf】中有着详细的解释,在第二个实例类中有着多个字段的对比写法,这俩个实例可以但对比观看,查看其中区别和奥妙。在下面的文字中我会简单写出UDAF的番薯执行顺序和功能。
执行顺序,和我写的实例中的函数排列顺序一样
.a)中声明输入的类型,名字不用在意
b)中声明缓冲区中初始值的类型,名字不用在意
c)中声明输出类型
d)定义函数的确定性为真,这个的作用我没有看懂,在什么情况下可以为flase,读者知道的话,希望可以告诉我,我会留下联系方式
.e)初始化缓冲区的第一个值
.f)对分区/线程中的值进行操作.g
)分区/线程间的结果进行合并处理.h
)输出最后的结果
.i)在主函数中定义该类的函数,并在spark_sql中直接使用。
在主函数中定义如下:
//生成collect_set函数,针对列级一个字段 整合为一个字段的函数 sqlContext.udf.register("collect_set", new MyUDAF) //生成collect_sets函数,针对列级多字段 整合为一个字段的函数 sqlContext.udf.register("collect_sets",new Collect_Set)