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. 三者的区别
- RDD:
1)RDD一般和spark mlib同时使用
2)RDD不支持sparksql操作
- 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)可以自由指定。
- 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
}