SparkSQL

============SparkSQL的前身Shark概述=================================

在三四年前,Hive可以说是SQL on Hadoop的唯一选择,负责将SQL编译成可扩展的MapReduce作业。鉴于Hive的性能以及与Spark的兼容,Shark项目由此而生。

Shark即Hive on Spark,本质上是通过Hive的HQL解析,把HQL翻译成Spark上的RDD操作,然后通过Hive的metadata获取数据库里的表信息,实际HDFS上的数据和文件,会由Shark获取并放到Spark上运算。

Shark的最大特性就是快和与Hive的完全兼容,且可以在shell模式下使用rdd2sql()这样的API,把HQL得到的结果集,继续在scala环境下运算,支持自己编写简单的机器学习或简单分析处理函数,对HQL结果进一步分析计算。

=====================SparkSQL==========================

Spark为结构化数据处理引入了一个称为Spark SQL的编程模块。它提供了⼀个称为DataFrame的编程抽象,并且可以充当分布式SQL查询引擎

Spark SQL的特点
1、支持多种数据源:Hive、RDD、Parquet、JSON、JDBC等。
2、多种性能优化技术:in-memory columnar storage、byte-code generation、cost model动态评估等。
3、组件扩展性:对于SQL的语法解析器、分析器以及优化器,用户都可以自己重新开发,并且动态扩展。

====================SparkSQL的应用和Hive集成================

基于SparkSQL的应用和Hive集成(基于SparkSQL的应用需要Hive表的元数据)
  -1. 将hive的hive-site.xml文件复制或者软连接到spark的conf文件夹中
      =======> 将hive-site.xml文件放到spark应用的classpath环境变量中
 cd /opt/cdh-5.3.6/spark/conf
 ln -s /opt/cdh-5.3.6/hive/conf/hive-site.xml
  -2. 根据hive-site.xml的配置文件内容,选择不同的操作方式
      根据hive.metastore.uris参数的配置项,默认为空
 -a. 如果没有给定参数值(默认情况)
   将hive元数据库的连接驱动包添加到Spark应用的classpath环境变量中即可完成sparksql应用和hive的集成
 -b. 如果给定了具体的metastore的服务所在节点的信息(非空)
   --1. 启动hive的metastore服务  &表示在后台运行
   cd /opt/cdh-5.3.6/hive/
bin/hive --service metastore &
   --2. 完成spark和hive的集成
注意:Spark应用是讲很多HQL语句放在一个SPARK应用里面执行,Hive ON Spark 是讲一条HQL语句放一个应用去执行。

SparkSQL和Hive集成测试
   cd /opt/cdh-5.3.6/spark
   bin/spark-shell
scala> sqlContext.sql("select * from emp a join dept b on a.deptno=b.deptno").show
scala> sqlContext.sql("select * from emp").show
   bin/spark-sql
use common;
select * from emp a join dept b on a.deptno=b.deptno;

=================Spark应用依赖第三方jar文件解决方案================

-1. 使用参数--jars添加本地的第三方jar文件,可以给定多个,使用逗号分隔
  注意:要求driver运行的机器上必须存在该jar文件
bin/spark-shell --jars /opt/cdh-5.3.6/hive/lib/mysql-connector-java-5.1.27-bin.jar,/opt/cdh-5.3.6/hive/lib/derby-10.10.1.1.jar
-2. 使用--packages添加maven中央库的第三方jar文件,可以给定多个,使用逗号分隔(利用maven的conf/settings.xml配置文件)
  注意:将第三方的jar文件下载后保存到本地的~/.ivy2/jars文件夹中(如果文件夹夹中存在,那么就不会下载直接使用)
  bin/spark-shell --packages mysql:mysql-connector-java:5.1.27
    -3. 在应用中设置spark.jars参数,参数值为本地的jar文件路径(运行过程中会自动的进行提交操作)
-4. 使用SPARK_CLASSPATH环境变量给定jar文件所在的路径
  内部添加的是spark应用的两个参数:spark.driver.extraClassPath和spark.executor.extraClassPath
  注意:要求所有执行节点上均存在对应文件夹及对应的jar文件
  cd /opt/cdh-5.3.6/spark
  mkdir -p external_jars
  vim conf/spark-env.sh
SPARK_CLASSPATH=/opt/cdh-5.3.6/spark/external_jars/*
  cd external_jars
  cp /opt/cdh-5.3.6/hive/lib/mysql-connector-java-5.1.27-bin.jar .
  cp /opt/cdh-5.3.6/hive/lib/derby-10.10.1.1.jar  .
-5. 将依赖的jar文件打包到最终的jar文件中

注意:Spark On Yarn Cluster运行模式的情况下,第三方jar文件依赖解决访问(推荐):
 将第三方的jar文件copy到${HADOOP_HOME}/share/hadoop/common/lib或者${HADOOP_HOME}/share/hadoop/yarn/lib文件夹中

=======================================================
SparkCore
  SparkContext
  RDD

SparkSQL
  SQLContext: SparkSQL的入口,依赖SparkContext
    HiveContext:
 SQLContext的子类,当SparkSQL和Hive集成的时候(访问hive元数据、使用HQL语法),必须使用HiveContext作为SparkSQL的入口
    
  DataFrame:SparkSQL中的核心抽象,类似RDD
   
=================SparkSQL的ThriftServer服务==============================

SparkSQL的ThriftServer服务
   也就是Hive的hiveserver2服务
   http://spark.apache.org/docs/1.6.1/sql-programming-guide.html#distributed-sql-engine
   https://cwiki.apache.org/confluence/display/Hive/Setting+Up+HiveServer2
   https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Overview
   
   配置:
    -1. ThriftServer服务所在的机器已经完成了SparkSQL和Hive集成的操作
    -2. 根据自己的需要修改hive-site.xml中hiveserver2服务的相关参数
       hive.server2.thrift.port=10000
  hive.server2.thrift.bind.host=localhost
-3. 启动thriftserver的服务
  sbin/start-thriftserver.sh 
-4. 关闭thriftserver服务
  sbin/stop-thriftserver.sh
   测试:
     用户名要求具有操作HDFS文件的权限,如果hdfs权限没有开启,用户名任意
     -1. Web UI
   ThriftServer服务还是一个Spark应用,所以Spark应用所有的东西在thriftserver中都可以使用(优化、监控....)
-2. 通过Spark自带的beeline命令
bin/beeline   
beeline> !connect jdbc:hive2://bigdata-01.yushu.com:10000
Connecting to jdbc:hive2://bigdata-01.yushu.com:10000
Enter username for jdbc:hive2://bigdata-01.yushu.com: hadoop
Enter password for jdbc:hive2://bigdata-01.yushu.com:10000: ******
17/07/09 12:00:23 INFO jdbc.Utils: Supplied authorities: bigdata-01.yushu.com
17/07/09 12:00:23 INFO jdbc.Utils: Resolved authority: bigdata-01.yushu.com
17/07/09 12:00:24 INFO jdbc.HiveConnection: Will try to open client transport with JDBC Uri: jdbc:hive2://bigdata-01.yushu.com:10000
Connected to: Spark SQL (version 1.6.1)
Driver: Spark Project Core (version 1.6.1)
Transaction isolation: TRANSACTION_REPEATABLE_READ
0: jdbc:hive2://hadoop-senior01:10000>    
0: jdbc:hive2://hadoop-senior01:10000> use common;
0: jdbc:hive2://hadoop-senior01:10000> select * from dept a join emp b on a.deptno = b.deptno;


退出命令: !quit
     -3. 通过jdbc的代码来测试
   http://maven.aliyun.com/nexus/content/groups/public/
http://mvnrepository.com/search?q=hive+jdbc

  org.spark-project.hive
  hive-jdbc
  0.13.1


================SparkSQL读取JSON格式的HDFS文件===========================

-1. 将测试文件上传HDFS
    hdfs dfs -mkdir -p /user/yushu/spark/sql/data
hdfs dfs -put /opt/cdh-5.3.6/spark/examples/src/main/resources/*   /user/yushu/spark/sql/data
  -2. 编写SparkSQL程序
val path = "/user/yushu/spark/data/people.json"
val df = sqlContext.jsonFile(path)
df.registerTempTable("tmp_people")
sqlContext.sql("select age,name from tmp_people where age is not null").show

sqlContext.sql("select age,name from json.`/user/yushu/spark/data/people.json` where age is not null").show

===================DataFrame====================================
  内部是以RDD为基础的分布式数据集,区别是:DataFrame包含了一个schema的信息
     RDD: 数据,DataFrame中的RDD类型其实就是RDD[Row],eg: df.rdd
     Schema: 数据对应的结构,也就是RDD中Row对应的数据结构,eg: df.schema
  创建的方式:
     Spark1.3之后才出现DataFrame概念
val df = sqlContext.##
  SparkSQL操作:
     -1. HQL/SQL开发
  将DataFrame注册成为临时表
  然后通过sqlContext.sql("sql语句")来进行操作
-2. DSL开发
  直接通过操作DataFrame的API进行业务开发,类似RDD的操作
  DataFrame数据输出:
     -1. 将DataFrame转换为RDD然后进行数据输出 1、saveAS...  2、foreach.....
-2. 直接调用DataFrame的相关API进行数据输出
      df.show()
df.collect()
df.write.#
df.#


SparkSQL应用的处理过程
  -1. 数据读入形成DataFrame
  -2. DataFrame数据操作,得到最终的DataFrame结果
  -3. 最终的DataFrame结果数据输出
  
DataFrame内部实际上是一个逻辑计划
  所有的数据执行都是一个lazy的操作
  调用相关的API的时候,实质是在内部构建一个查询的逻辑计划,类似RDD的构建过程;只有当DataFrame被触发调用的时候(获取数据的操作),才真正的执行

=================DataFrame的read和write编程模式=================

  https://spark-packages.org/
  https://github.com/databricks
  功能:通过SparkSQL内部定义的read和write数据读写入口进行数据的加载和保存操作
  读取数据:
    val df = sqlContext.read.####.load()
  写出数据:
    df.write.####.save()

Read:
  功能:读取外部数据源的数据,并形成DataFrame
  相关函数说明:

  format:给定读取数据源数据的格式是什么(那种什么形式读取数据)
schema: 给定数据的数据格式是什么,如果不给定,会自动进行推断
option:给定读取数据过程中所需要的相关参数
load:加载数据形成DataFrame(除了JDBC的数据源)
jdbc:加载RDBMs的数据形成DataFrame
table:加载表数据形成DataFrame

eg:sqlContext.read.format("parquet").load("/yushu/spark/sql/join").show

三种不同jdbcAPI讲解:
def jdbc(url: String, table: String, properties: Properties): DataFrame
  根据给定的url、table和连接信息读取对应表的数据,使用一个分区来读取数据
def jdbc(
      url: String, 给定url连接信息
      table: String, 给定从哪个表获取数据
      columnName: String, 给定进行分区的分区字段
      lowerBound: Long, 给定计算的下界
      upperBound: Long, 给定计算的上界
      numPartitions: Int, 给定分区数量
      connectionProperties: Properties--连接信息
 ): DataFrame
最终形成的DataFrame分区数量和参数numPartitions一致;要求分区字段是数值型的
 step = (upperBound - lowerBound) / numPartitions
 currentIndex = step + lowerBound ==> 第一个分区:(负无穷大,currentIndex)
 preIndex = currentIndex
 currentIndex += step ===> 第二个分区: [preIndex, currentIndex)
 ........ ==> 直到分区数量为numPartitions - 1
 得到最后一个分区: [currentIndex, 正无穷大)
def jdbc(
      url: String,
      table: String,
      predicates: Array[String], 给定数据获取的where条件,最终的分区数量就是array中数据的个数
      connectionProperties: Properties): DataFrame
  
Write:
  功能:将DataFrame的数据写出到指定的目的地
  相关函数说明:
    mode:指定数据插入的策略(当数据插入的文件夹或者表存在的时候怎么做?)
 case "overwrite" => SaveMode.Overwrite: 覆盖(原来的删除,然后插入)
          case "append" => SaveMode.Append: 追加
          case "ignore" => SaveMode.Ignore: 不进行插入操作
          case "error" | "default" => SaveMode.ErrorIfExists: 报错
   format:给定数据存储时候的数据格式
   option:给定存储过程中所使用到的相关参数
   partitionBy:给定hive表数据存储的时候,分区字段是那个
   save:触发数据保存操作
   insertInto:插入到一张已经存在的hive表中
   saveAsTable:将DataFrame的数据保存到一张hive表中
   jdbc:将数据写入到RDBMs中

================== Windows上运行SparkSQL应用程序==============================
   pom.xml文件中添加依赖

  org.apache.spark
  spark-hive_2.10
  ${spark.version}
  compile
  

mysql
mysql-connector-java
5.1.27


Windows上运行SparkSQL应用程序
   1. 添加hive-site.xml配置文件到应用的classpath中,并启动hive的metasotre服务或者添加元数据的驱动包
   2. Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
     解决方案:run -> edit configxxxx给定运行的JVM参数,-XX:PermSize=128M -XX:MaxPermSize=128M
   3. 可能出现的问题,由于hadoop在windows上和linux上执行的方式不同,在使用HiveContext的过程中,有可能出现NullPointException异常,原因是源码问题,解决方案:修改源码
   4. 如果你配置了HADOOP_USER_NAME的环境变量,请删除(你的操作系统用户名正常)
 5.  [ERROR - org.apache.spark.Logging$class.logError(Logging.scala:95)] Exception while deleting Spark temp dir: C:\Users\yushu\AppData\Local\Temp\spark-52123462-58f6-4c78-b3f7-53f08355ec6e
java.io.IOException: Failed to delete: C:\Users\yushu\AppData\Local\Temp\spark-52123462-58f6-4c78-b3f7-53f08355ec6e
     产生原因是由于当SparkSQL执行完成后,会删除临时文件夹中的文件(windows上没有权限删除),不影响,不用管该异常
   6. 如果用户名为中文或者用户名中存在着空格,记住设置一下相关环境变量:
           hdfs的配置:hadoop.tmp.dir给定一个值
  必须配置HADOOP_USER_NAME的环境变量

====================== RDD to DataFrame===================================

  根据一个已经存在的RDD来创建一个DataFrame对象
  http://spark.apache.org/docs/1.6.1/sql-programming-guide.html#interoperating-with-rdds
  -1. Inferring the Schema Using Reflection
    利用schema的反射机制来进行创建操作
要求:RDD中的数据类型必须是case class数据类型,而且必须引入隐式转换
 import sqlContext.implicits._
  -2. Programmatically Specifying the Schema
    调用SQLContext的相关API,根据给定的RDD和schema来进行DataFrame的创建
要求:RDD中的数据类型必须是Row类型的  
valdf = rdd.toDF()

====================SparkSQL函数=================================

  对于Hive中的窗口分析函数,必须使用HiveContext作为入口
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+WindowingAndAnalytics
  ROW_NUMBER ===> 对每组数据按照给定的字段进行排序后,赋予从1开始的序列号
   ===> 解决分组排序TopN程序最优方案
sqlContext.sql("select deptno,sal,row_number() over (partition by deptno order by sal desc) as rnk from common.emp").show()
+------+------+---+
|deptno|   sal|rnk|
+------+------+---+
|    10|5000.0|  1|
|    10|2450.0|  2|
|    10|1300.0|  3|

|    20|3000.0|  1|
|    20|3000.0|  2|
|    20|2975.0|  3|
|    20|1100.0|  4|
|    20| 800.0|  5|

|    30|2850.0|  1|
|    30|1600.0|  2|
|    30|1500.0|  3|
|    30|1250.0|  4|
|    30|1250.0|  5|
|    30| 950.0|  6|
+------+------+---+
sqlContext.sql("select * from (select deptno,sal, row_number() over (partition by deptno order by sal desc) as rnk from common.emp) t where t.rnk <=3").show()
+------+------+---+
|deptno|   sal|rnk|
+------+------+---+
|    10|5000.0|  1|
|    10|2450.0|  2|
|    10|1300.0|  3|

|    20|3000.0|  1|
|    20|3000.0|  2|
|    20|2975.0|  3|

|    30|2850.0|  1|
|    30|1600.0|  2|
|    30|1500.0|  3|
+------+------+---+
====================================================================

Dataset

  创建方式
    http://spark.apache.org/docs/1.6.1/sql-programming-guide.html#creating-datasets
  Dataset、DataFrame、RDD的区别与联系:
    相同点:
 都是分布式数据集
 数据是分区的不可变的
 DataFrame和Dataset的相同点还有:都具有数据特征/数据类型(schema)
   不同点:
 RDD中的数据没有数据类型
 DataFrame中的数据是弱数据类型的,在运行之前不会做类型检查
 Dataset中的数据类型是强数据类型的
 Dataset中序列化机制是Kryo,RDD和DataFrame中是java的序列化机制
 Dataset的运行速度比DataFrame/RDD快
==================SparkSQL优化===========================

  -1. SparkCore中用到的优化相关的东西,SparkSQL经常使用
  -2. SparkSQL的专门的优化
        -a. hql语句的优化
        -b. 参数优化
  http://spark.apache.org/docs/1.6.1/sql-programming-guide.html#performance-tuning
       spark.sql.shuffle.partitions:200, 给定hql语句中如果存在join\xxByKey的操作的时候(也就是存在shuffle的时候),DataFrame的分区数量

总结:
  SparkSQL应用和Hive集成的方式
  DataFrame是啥
  DataFrame、Dataset、RDD的区别与联系
  SparkSQL的write和read编程模型
  SparkSQL使用DSL语言开发
  案例:
     不同数据源数据的操作
     分区排序TopN的实现
  

========================= 不同数据源数据的JOIN操作=================================

object HiveJoinMysqlDemo {
  def main(args: Array[String]) {
    //构建上下文
    val conf=new SparkConf()
        .setAppName("HiveJoinMysql")
        .setMaster("local[*]")
    val sc=SparkContext.getOrCreate(conf)
    val sqlContext=new HiveContext(sc)
    //连接mysql数据库
    val url="jdbc:mysql://localhost:3306/yushu"
    val table="tb_dept"
    val props=new Properties()
    props.put("user","root")
    props.put("password","123456")

    // 2. 将dept数据同步到mysql中
    sqlContext
        .read
        .table("db_emp.dept")
        .write
        .mode(SaveMode.Overwrite)
        .jdbc(url,table,props)

    // 3. mysql数据和hive数据join
    // 将mysql的数据读取形成DataFrame,然后注册成为临时表
    val df = sqlContext
      .read
      .jdbc(url, table, Array("loc <= 'CHICAGO'", "loc > 'CHICAGO'"), props)
    df.registerTempTable("mysql_dept")
    sqlContext
      .sql("select a.*, b.loc,b.dname from db_emp.emp a join mysql_dept b on a.deptno=b.deptno")
      .registerTempTable("result_tmp01")

    // 3. 将数据写到HDFS上形成parquet格式的数据
    sqlContext
      .table("result_tmp01")
      .write
      .format("parquet")
      .mode(SaveMode.Overwrite)
      .save("/user/yushu/spark/data")
//按部门分区保存在Hive表中
sqlContext .table( "result_tmp01") .write .format( "parquet") .partitionBy( "deptno") .mode(SaveMode. Overwrite) .saveAsTable( "test01") }} ====================== SparkSQLThriftServerJDBCDemo=========================

def main(args: Array[String]): Unit = {
  // 1. 添加driver
  val driver = "org.apache.hive.jdbc.HiveDriver"
  Class.forName(driver)

  // 创建连接
  val url = "jdbc:hive2://bigdata-01.yushu.com:10000"
  val conn = DriverManager.getConnection(url, "gerry", "123456")

  // 执行sql
  conn.prepareStatement("use db_emp").execute()
  val pstmt = conn.prepareStatement("select * from dept a join emp b on a.deptno = b.deptno where b.sal > ?")
  pstmt.setInt(1, 2500)
  val rs = pstmt.executeQuery()
  while (rs.next()) {
    println(rs.getString("ename") + ":" + rs.getDouble("sal"))
  }

  // 关闭连接
  rs.close()
  pstmt.close()
  conn.close()
}
========================= ReadCSVSparkSQL ============================
import org.apache.spark.sql.hive.HiveContext
import org.apache.spark.sql.types.{StringType, IntegerType, StructField, StructType}
import org.apache.spark.sql.{SaveMode, SQLContext}
import org.apache.spark.{SparkContext, SparkConf}
object ReadCSVSparkSQL {
  def main(args: Array[String]): Unit = {

    val conf = new SparkConf()
      .setAppName("ReadCSVSparkSQL")
      .setMaster("local[*]")
      .set("spark.sql.shuffle.partitions", "4")
    val sc = SparkContext.getOrCreate(conf)
    val sqlContext = new HiveContext(sc)
    val time = System.currentTimeMillis()

    // 2. 读取csv文件然后形成DataFrame,然后将DataFrame注册成为临时表tmp_taxi
    // 列名称:id,lat, lon, time
    val schema = StructType(Array(
      StructField("id", IntegerType),
      StructField("lat", StringType),
      StructField("lon", StringType),
      StructField("time", StringType)
    ))
    val path = "datas/taxi.csv"
    val df = sqlContext
      .read
      .format("com.databricks.spark.csv")
      .schema(schema)
      .load(path)
    df.registerTempTable("tmp_taxi")
    // 3.1 获取需要进行分析的列字段(id、time),并将time转换为小时
    sqlContext.sql(
      """
        |select id,substring(time,0,2) as hour
        |from tmp_taxi
      """.stripMargin
    ).registerTempTable("tmp01")
    // 3.2 统计各个出租车在各个时间段的载客次数
    sqlContext.sql(
      """
        |select id,hour,COUNT(1) as cnt
        |from tmp01
        |group by id,hour
      """.stripMargin).registerTempTable("tmp02")
    // 3.3 获取每个小时段的载客次数前10的出租车数据(先按照小时分组,然后对每组数据获取前10条出租车载客数据<最多的>) ---> 分组排序TopN --> row_number函数
    sqlContext.sql(
      """
        |select id,hour,cnt,
        |ROW_NUMBER() OVER(PARTITION BY hour ORDER BY cnt DESC) as rnk
        |FROM tmp02
      """.stripMargin).registerTempTable("tmp03")
    sqlContext.sql(
      """
        |select id,hour,cnt
        |from tmp03
        |where rnk<=10
      """.stripMargin).registerTempTable("tmp04")

    //4.将结果保存
    sqlContext.cacheTable("tmp04")
    sqlContext
      .table("tmp04")
      .map(row => {
      val id = row.getAs[Int]("id")
      val hour = row.getAs[String]("hour")
      val cnt = row.getAs[Long]("cnt")
      (id, hour, cnt)
    }).saveAsTextFile(s"result/csv/${time}/1")

    sqlContext
      .table("tmp04")
      .write
      .mode(SaveMode.Overwrite)
      .format("com.databricks.spark.csv")
      .save(s"result/csv/${time}/2")

    Thread.sleep(100000)
  }
}
=====================RDD2DataFrameSparkSQ============================
 
  
object RDD2DataFrameSparkSQL {
  def main(args: Array[String]): Unit = {
    // 1. 构建上下文
    val conf = new SparkConf()
      .setAppName("RDD2DataFrameSparkSQL")
      .setMaster("local[*]")
    // 由于SparkContext在JVM中只能存在一份,所以尽量不要使用new方式来创建
    val sc = SparkContext.getOrCreate(conf)
    val sqlContext = new SQLContext(sc)
    import sqlContext.implicits._

    // 2. 方式一:反射的机制
    // alt + enter 快速导入变量的数据类型(鼠标放在变量后面)
    val rdd: RDD[Person] = sc.parallelize(Array(
      Person("gerry", 12),
      Person("小刘", 15),
      Person("小明", 18)
    ))
    val df = rdd.toDF() // 默认以case class的属性名称作为DataFrame的列名称
    df.show()
    // 也可以通过toDF的参数来给定列名称,但是要求给定的数组集合中的元素和原始的列数量一致
    rdd.toDF("c1", "c2").show()
    sc.parallelize(Array(
      ("gerry", 12),
      ("小刘", 15),
      ("小明", 18)
    )).toDF().show()

    // 方式二:利用明确给定的schema和rdd进行构建
    val rowRDD = rdd.map(p => {
      val name = p.name
      val age = p.age
      Row(name, age)//构建Row类型的RDD
    })
    val schema = StructType(Array(
      StructField("name", StringType),
      StructField("age", IntegerType)//构建Schema
    ))
    val df2 = sqlContext.createDataFrame(rowRDD, schema)
    df2.show()


    // DataFrame -> RDD
    val rdd1: RDD[Row] = df2.rdd
    val rdd2: RDD[(String, Int)] = df2.map(row => {
      // Row中的数据是没有数据类型的
      val name = row.getAs[String]("name")
      val age = row.getAs[Int]("age")
      (name, age)
    })
    rdd2.foreachPartition(iter => iter.foreach(println))
  }
}

case class Person(name: String, age: Int)
=======================UDFAndUDAFSparkSQL ================================
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.SQLContext
object UDFAndUDAFSparkSQL {
  def main(args: Array[String]): Unit = {
    // 1. 构建上下文
    val conf = new SparkConf()
      .setAppName("UDFAndUDAFSparkSQL")
      .setMaster("local[*]")
    // 由于SparkContext在JVM中只能存在一份,所以尽量不要使用new方式来创建
    val sc = SparkContext.getOrCreate(conf)
    val sqlContext = new SQLContext(sc)
    import sqlContext.implicits._
    // 自定义UDF
    sqlContext.udf.register("format_double", (v:Double, i:Int) => {
      import java.math.BigDecimal
      val bd = new BigDecimal(v)
      bd.setScale(i, BigDecimal.ROUND_HALF_UP).doubleValue()
    })
    // udaf注册
    sqlContext.udf.register("self_avg", SelfAvgUDAF)

    // 创建一个DataFrame并注册成为临时表
    sc.parallelize(Array(
      (1, 1234.123),
      (1, 4521.125),
      (1, 4523.0),
      (1, 562.0),
      (2, 1235.0),
      (2, 1255.235),
      (2, 1412.253),
      (2, 124.0),
      (2, 143.235),
      (3, 4562.0),
      (3, 1252.0),
      (3, 125.0),
      (3, 1154.0),
      (4, 1263.235),
      (4, 1246.0),
      (4, 1252.253),
      (4, 1451.0),
      (4, 1223.0),
      (5, 1263.235),
      (5, 1253.0),
      (6, 1235.0)
    ))
      .toDF("id", "sal")
      .registerTempTable("tmp01")

    sqlContext.sql(
      """
        |SELECT
        |  id,
        |  AVG(sal) as sal1,
        |  format_double(AVG(sal), 2) as sal2,
        |  format_double(self_avg(sal), 2) as sal3
        |FROM
        |  tmp01
        |GROUP BY id
      """.stripMargin)
      .show()
  }
}
===============SelfAvgUDAF对象================
import org.apache.spark.sql.Row
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types._


object SelfAvgUDAF extends UserDefinedAggregateFunction{
  override def inputSchema: StructType = {
    // 给定输入的数据类型
    StructType(
      Array(
        StructField("v1", DoubleType)
      )
    )
  }

  override def bufferSchema: StructType = {
    // 给定缓存过程中的数据类型
    StructType(
      Array(
        StructField("b1", DoubleType),
        StructField("b2", IntegerType)
      )
    )
  }

  override def dataType: DataType = {
    // 给定函数的返回数据类型
    DoubleType
  }

  override def deterministic: Boolean = {
    // 指定多次运行的结果是否必须一致
    true
  }

  override def initialize(buffer: MutableAggregationBuffer): Unit = {
    // 初始化缓存区中的
    buffer.update(0, 0.0)
    buffer.update(1, 0)
  }

  override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
    // 对于输入的数据input更新buffer的值
    val iv = input.getDouble(0)

    val bsum = buffer.getDouble(0)
    val btotal = buffer.getInt(1)

    buffer.update(0, bsum + iv)
    buffer.update(1, btotal + 1)
  }

  /**
    * 该方法只有当多个分区存在的时候才会被调用(对多个分区的聚合结果做几个全局的聚合操作)
    * @param buffer1
    * @param buffer2
    */
  override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
    // 将buffer2中的值合并到buffer1中
    val sum1 = buffer1.getDouble(0)
    val total1 = buffer1.getInt(1)

    val sum2 = buffer2.getDouble(0)
    val total2 = buffer2.getInt(1)

    buffer1.update(0, sum1 + sum2)
    buffer1.update(1, total1 + total2)
  }

  override def evaluate(buffer: Row): Any = {
    // 计算最终结果
    buffer.getDouble(0) / buffer.getInt(1)
  }
}


你可能感兴趣的:(大数据)