Spark -- SparkSql编程

本文详细介绍Spark SQL编程,涵盖DataFrame和DataSet的创建与操作,包括从数据源创建、从RDD转换及从HiveTable查询返回DataFrame。同时对比了RDD、DataFrame和DataSet的特性,介绍了用户自定义函数的使用。
摘要由CSDN通过智能技术生成

Spark – SparkSql编程


SparkSession是Spark最新的SQL查询起始点,实质上是SQLContext和HiveContext的组合,所以在SQLContext和HiveContext上可用的API在SparkSession上同样是可以使用的。SparkSession内部封装了sparkContext,所以计算实际上是由sparkContext完成的。

1. DataFrame

1.1 创建

在Spark SQL中SparkSession是创建DataFrame和执行SQL的入口,创建DataFrame有三种方式:通过Spark的数据源进行创建;从一个存在的RDD进行转换;还可以从Hive Table进行查询返回。

1.1.1 通过spark的数据源创建
scala> spark.read.
csv   format   jdbc   json   load   option   options   orc   parquet   schema   table   text   textFile

scala> val df=spark.read.json("/home/hadoop/user.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]

scala> df.show()
+---+--------+
|age|    name|
+---+--------+
| 12|zhangsan|
| 22|    lisi|
| 11|  wanger|
| 33|    mazi|
+---+--------+
1.1.2 从RDD进行转换
1.1.3 从Hive Table进行查询返回

1.2 SQL语法风格

scala> val df=spark.read.json("/home/hadoop/user.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]

//创建一个临时表
scala> df.createOrReplaceTempView("stu")

scala> val dfsql=spark.sql("select * from stu")
dfsql: org.apache.spark.sql.DataFrame = [age: bigint, name: string]

scala> dfsql.show
+---+--------+
|age|    name|
+---+--------+
| 12|zhangsan|
| 22|    lisi|
| 11|  wanger|
| 33|    mazi|
+---+--------+


//java编程
 def main(args: Array[String]): Unit = {
    //SparkConf,创建配置对象
    val sparkconf=new SparkConf().setMaster("local[*]").setAppName("SparkSql01_Demo")
    //创建sparksql环境对象
    //    val spark: SparkSession = new SparkSession(sparkconf)   错误
    val spark: SparkSession = SparkSession.builder().config(sparkconf).getOrCreate()

    //读取数据为DF
    val frame: DataFrame = spark.read.json("in/user.json")

    //将DF数据转换为一个临时视图
    frame.createOrReplaceTempView("student")

    //采用sql的方式访问此视图
    spark.sql("select * from student").show()

    spark.stop()
  }

注意:临时表是Session范围内的,Session退出后,表就失效了。如果想应用范围内有效,可以使用全局表。注意使用全局表时需要全路径访问,如:global_temp.people

scala> df.createGlobalTempView("gstu")

scala> spark.sql("select * from global_temp.gstu").show
+---+--------+
|age|    name|
+---+--------+
| 12|zhangsan|
| 22|    lisi|
| 11|  wanger|
| 33|    mazi|
+---+--------+

1.3 DSL语法风格

1)创建一个DateFrame
scala> spark.read.
csv   format   jdbc   json   load   option   options   orc   parquet   schema   table   text   textFile
2)查看DataFrame的Schema信息
scala> df.printSchema
root
 |-- age: long (nullable = true)
 |-- name: string (nullable = true)
3)只查看”name”列数据
scala> df.select("name").show()
+-------+
|   name|
+-------+
|Michael|
|   Andy|
| Justin|
+-------+
4)查看”name”列数据以及”age+1”数据
scala> df.select($"name", $"age" + 1).show()
+-------+---------+
|   name|(age + 1)|
+-------+---------+
|Michael|     null|
|   Andy|       31|
| Justin|       20|
+-------+---------+
5)查看”age”大于”21”的数据
scala> df.filter($"age" > 21).show()
+---+----+
|age|name|
+---+----+
| 30|Andy|
+---+----+
6)按照”age”分组,查看数据条数
scala> df.groupBy("age").count().show()
+----+-----+
| age|count|
+----+-----+
|  19|     1|
|null|     1|
|  30|     1|
+----+-----+

1.1.4 RDD转换为DataFrame

RDD->DataFrame:只需要加上属性,RDD调用toDF()方法,参数就是属性,例如toDF(“name”,“age”)

RDD->DataSet:需要加上属性和类型,方式一:RDD先调用toDF()转化为DF,然后创建样例类,DF调用as[]方法传入样例类,这样就传入了属性和类型,转换为了DS;方式二:创建样例类,RDD调用map算子,传入属性和类型,再调用toDS()方法转换为DS。

DataFrame->RDD:DF直接调用rdd()方法

DataSet->RDD:先调用toDF()转换为DF,再调用rdd()转换为rdd

import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}

object sppark_transfrom {
  def main(args: Array[String]): Unit = {
    val sparkconf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("transfrom")
    val spark: SparkSession = SparkSession.builder().config(sparkconf).getOrCreate()

    //创建RDD
    val rdd: RDD[(Int, String)] = spark.sparkContext.makeRDD(List((1,"zs"),(2,"ls"),(3,"ss")))
    //转化为DF
    //进行转化之前要引入隐式转换的规则,这里的spark不是包名的名字,是sparkSession对象的名字
    import spark.implicits._

//    val df: DataFrame = rdd.toDF("id","name")
//    //转化为DS
//    val ds: Dataset[User] = df.as[User]
//    //转化回DF
//    val dsTodf = ds.toDF()
//
//    //转化为rdd,直接调用rdd即可,转换之后只有结构没有属性
//    val dsTordd: RDD[Row] = dsTodf.rdd
//
//    dsTordd.foreach{
//          //通过索引访问数据
//      row => println(row.getString(1))
//    }

    //rdd一步转化为DS
    val mapRDD: RDD[User] = rdd.map {
      t => {
        User(t._1, t._2)
      }
    }
    val ds: Dataset[User] = mapRDD.toDS()
    ds.show()

    //释放资源
    spark.stop()
  }
}
case class User(id:Int,name:String)

2. DataSet

Dataset是具有强类型的数据集合,需要提供对应的类型信息。

2.1 创建

1)创建一个样例类

scala> case class Person(name: String, age: Long)

defined class Person

2)创建DataSet

scala> val caseClassDS = Seq(Person("Andy", 32)).toDS()

caseClassDS: org.apache.spark.sql.Dataset[Person] = [name: string, age: bigint]


//或者通过RDD  DF转换为DS

3. RDD,DataSet和DataFrame的区别和共性

在这里插入图片描述在这里插入图片描述

在SparkSQL中Spark为我们提供了两个新的抽象,分别是DataFrame和DataSet。他们和RDD有什么区别呢?首先从版本的产生上来看:RDD (Spark1.0) —> Dataframe(Spark1.3) —> Dataset(Spark1.6)

如果同样的数据都给到这三个数据结构,他们分别计算之后,都会给出相同的结果。不同是的他们的执行效率和执行方式。

在后期的Spark版本中,DataSet会逐步取代RDD和DataFrame成为唯一的API接口。

1. 三者的共性

1、RDD、DataFrame、Dataset全都是spark平台下的分布式弹性数据集,为处理超大型数据提供便利

2、三者都有惰性机制,在进行创建、转换,如map方法时,不会立即执行,只有在遇到Action如foreach时,三者才会开始遍历运算。

3、三者都会根据spark的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出。

4、三者都有partition的概念

5、三者有许多共同的函数,如filter,排序等

6、在对DataFrame和Dataset进行操作许多操作都需要这个包进行支持

import spark.implicits._

7、DataFrame和Dataset均可使用模式匹配获取各个字段的值和类型

2. 三者的区别

  1. RDD:

1)RDD一般和spark mlib同时使用

2)RDD不支持sparksql操作

  1. DataFrame:

1)与RDD和Dataset不同,DataFrame每一行的类型固定为Row,每一列的值没法直接访问,只有通过解析才能获取各个字段的值,如:

testDF.foreach{
  line =>
    val col1=line.getAsString
    val col2=line.getAsString
}

2)DataFrame与Dataset一般不与spark mlib同时使用

3)DataFrame与Dataset均支持sparksql的操作,比如select,groupby之类,还能注册临时表/视窗,进行sql语句操作,如:


4)DataFrame与Dataset支持一些特别方便的保存方式,比如保存成csv,可以带上表头,这样每一列的字段名一目了然

//保存

val saveoptions = Map("header" -> "true", "delimiter" -> "\t", "path" -> "hdfs://hadoop102:9000/test")

datawDF.write.format("com.atguigu.spark.csv").mode(SaveMode.Overwrite).options(saveoptions).save()

//读取

val options = Map("header" -> "true", "delimiter" -> "\t", "path" -> "hdfs://hadoop102:9000/test")

val datarDF= spark.read.options(options).format("com.atguigu.spark.csv").load()

利用这样的保存方式,可以方便的获得字段名和列的对应,而且分隔符(delimiter)可以自由指定。

  1. Dataset:

1)Dataset和DataFrame拥有完全相同的成员函数,区别只是每一行的数据类型不同。

2)DataFrame也可以叫Dataset[Row],每一行的类型是Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的getAS方法或者共性中模式匹配拿出特定字段。而Dataset中,每一行是什么类型是不一定的,在自定义了case class之后可以很自由的获得每一行的信息。Dataset在需要访问列中的某个字段时是非常方便的,然而,如果要写一些适配性很强的函数时,如果使用Dataset,行的类型又不确定,可能是各种case class,无法实现适配,这时候用DataFrame即Dataset[Row]就能比较好的解决问题

4. 用户自定义函数

//读取数据生成DataFrame
scala> val df=spark.read.json("/home/hadoop/user.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]

//生成一张临时表
scala> df.createOrReplaceTempView("stu")

//自定义函数并且进行注册
scala> spark.udf.register("addName",(x:String) => "Name:"+x)
res5: org.apache.spark.sql.expressions.UserDefinedFunction = UserDefinedFunction(<function1>,StringType,Some(List(StringType)))

//sarksql中使用自定义的函数
scala> spark.sql("select addName(name) ,age from stu").show()
+-----------------+---+
|UDF:addName(name)|age|
+-----------------+---+
|    Name:zhangsan| 12|
|        Name:lisi| 22|
|      Name:wanger| 11|
|        Name:mazi| 33|
+-----------------+---+
1. 用户自定义UDAF函数(弱类型)
import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, Row, SparkSession}
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types._

object SparkSql_UDF01 {
  def main(args: Array[String]): Unit = {
    val sparkconf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("transfrom")
    val spark: SparkSession = SparkSession.builder().config(sparkconf).getOrCreate()

    //进行转化之前要引入隐式转换的规则,这里的spark不是包名的名字,是sparkSession对象的名字
    import spark.implicits._
    val frame: DataFrame = spark.read.json("in/user.json")
    frame.createTempView("user")
    //创建聚合函数的对象
    val udaf: MyAgeAvgFun = new MyAgeAvgFun
    //注册聚合函数
    spark.udf.register("avgAge",udaf)

    //使用聚合函数
    spark.sql("select avgAge(age) from user").show

    //释放资源
    spark.stop()
  }
}

//声明用户自定义函数
//1)继承UserDefinedAggregateFunction
//2)实现方法
class MyAgeAvgFun extends UserDefinedAggregateFunction {

  //inputSchema:函数输入的数据结构
  override def inputSchema: StructType = {
    new StructType().add("age",LongType)
  }

  //bufferSchema:计算时的数据结构
  override def bufferSchema: StructType = {
    new StructType().add("sum",LongType).add("count",LongType)
  }

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

  //deterministic:是否稳定
  override def deterministic: Boolean = true

  //initialize:计算之前缓冲区的初始化
  override def initialize(buffer: MutableAggregationBuffer): Unit = {
    buffer(0)=0L
    buffer(1)=0L
  }

  //根据查询结果更新缓冲区数据
  override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
    buffer(0)= buffer.getLong(0)+input.getLong(0)
    buffer(1)=buffer.getLong(1)+1
  }

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

  //evaluate:计算逻辑
  override def evaluate(buffer: Row): Any = {
    //使用toDoubl使得返回的值一定是duble类型
    buffer.getLong(0).toDouble/buffer.getLong(1)
  }
}
2. 用户自定义聚合函数(强类型)
import org.apache.spark.SparkConf
import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql._

object SparkSql01_Demo {
  def main(args: Array[String]): Unit = {
    val sparkconf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("transfrom")
    val spark: SparkSession = SparkSession.builder().config(sparkconf).getOrCreate()

    //进行转化之前要引入隐式转换的规则,这里的spark不是包名的名字,是sparkSession对象的名字
    import spark.implicits._

    val frame: DataFrame = spark.read.json("in/user.json")
    val userDS: Dataset[UserBean] = frame.as[UserBean]

    //创建聚合函数对象
    val udaf: MyAgeAvgFunClass = new MyAgeAvgFunClass

    //将聚合函数转换为查询的列
    val avgCol: TypedColumn[UserBean, Double] = udaf.toColumn.name("avgAge")

    //无法使用sql风格,可以使用DSL风格,需要注意的是这里要转换为DS来调用DSL语法,因为这里的可查询的列avgCol是有具体属性和类型的,所以要转换为DS,引入样例类。
    userDS.select(avgCol).show()
    //释放资源
    spark.stop()
  }
}

case class UserBean(name:String,age:Int)
case class AvgBuffer(var sum:Int,var count:Int)


//声明用户自定义函数(强类型)
//1)继承Aggregator,设定泛型(增加样板类)
//2)实现方法
class MyAgeAvgFunClass extends Aggregator[UserBean,AvgBuffer,Double] {
  //初始化
  override def zero: AvgBuffer = {
    AvgBuffer(0,0)
  }

  //聚合数据,将输入的数据合并进缓冲区
  override def reduce(b: AvgBuffer, a: UserBean): AvgBuffer = {
    b.sum=b.sum+a.age
    b.count=b.count+1
    b
  }


  //缓冲区的合并操作
  override def merge(b1: AvgBuffer, b2: AvgBuffer): AvgBuffer = {
    b1.sum=b1.sum+b2.sum
    b1.count=b1.count+b2.count
    b1
  }

  //完成计算
  override def finish(reduction: AvgBuffer): Double = {
    reduction.sum.toDouble / reduction.count
  }

  //缓冲区编码
  override def bufferEncoder: Encoder[AvgBuffer] = Encoders.product
  //输出编码
  override def outputEncoder: Encoder[Double] = Encoders.scalaDouble
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值