Spark SQL是Spark用来处理结构化数据的一个模块,它提供了一个编程抽象叫做DataFrame并且作为分布式SQL查询引擎的作用
【2.1】易整合
Spark SQL可以使用SQL或熟悉的DataFrame API在Spark程序中查询结构化数据,可在Java,Scala,Python和R中使用
【2.2】统一的数据访问方式
DataFrame和SQL提供了一种访问各种数据源的通用方法,包括Hive,Avro,Parquet,ORC,JSON和JDBC。甚至可以跨这些源联接数据
【2.3】兼容Hive
Spark SQL支持HiveQL语法以及Hive SerDes和UDF,从而访问现有的Hive仓库
【2.4】标准的数据连接
【3.1】SparkSQL可以看做是一个转换层,向下对接各种不同的结构化数据源,向上提供不同的数据访问方式
【3.2】
在SparkSQL中Spark为我们提供了两个新的抽象,分别是DataFrame和DataSet;他们和RDD有什么区别呢?首先从版本的产生上来看:RDD (Spark1.0) —> Dataframe(Spark1.3) —> Dataset(Spark1.6)
如果同样的数据都给到这三个数据结构,他们分别计算之后,都会给出相同的结果。不同是的他们的执行效率和执行方式
注意:在后期的Spark版本中,DataSet会逐步取代RDD和DataFrame成为唯一的API接口
【3.3】RDD
【3.4】DataFrame
执行效率
、减少数据读取
以及执行计划的优化
,比如filter下推、裁剪等…懒执行
的,性能上比RDD要高,主要有两方面原因:定制化内存管理
数据以二进制的方式存在于非堆内存,节省了大量空间之外,还摆脱了GC的限制
劣势
在于在编译期缺少类型安全检查
,导致运行时出错【3.5】DataSet
类型安全检查
也具有DataFrame的查询优化特性
编解码器
,当需要访问非堆
上的数据时可以避免反序列化整个对象
,提高了效率as
方法将DataFrame转换为DataSet,也可以通过 toDF
方法将DataSet转换为DataFrameDataFrame只是知道字段,但是不知道字段的类型,所以在执行这些操作的时候是没办法在编译的时候检查是否类型失败的,比如你可以对一个String进行减法操作,在执行的时候才报错,而DataSet不仅仅知道字段,而且知道字段类型,所以有更严格的错误检查。就跟JSON对象和类对象之间的类比,如下图:
【3.6】RDD、DataFrame和DataSet三者的共性
val sparkconf = new SparkConf().setMaster("local[*]").setAppName("sparkSqlDemo")
val spark = SparkSession.builder().config(sparkconf).getOrCreate()
val rdd = spark.sparkContext.parallelize(Seq(("a", 1), ("b", 1), ("a", 1)))
// 下面这个map并不会运行
rdd.map{line=>
println("运行到这里啦...")
line._1
}
注意:spark不是包名,是SparkSession的名字
import spark.implicits._
DataFrame:
df.map {
case Row(id: Int, name: String, age: Int) => {
println(s"${id},${name},${age}")
name
}
case _ => ""
}
DataSet:
ds.map {
case User(id: Int, name: String, age: Int) => {
println(s"${id},${name},${age}")
name
}
case _ => ""
}
【3.7】RDD、DataFrame和DataSet三者的区别
RDD可以设置类型参数,但RDD并不了解其内部结构
1.与RDD和DataSet不同,DataFrame每一行的类型固定为Row,只有通过解析才能获取各个字段的值,如下:
df.foreach{
line => {
val id = line.getAs[Int]("id")
val name = line.getAs[String]("name")
val age = line.getAs[Int]("age")
println(s"${id} - ${name} - ${age}")
val id2 = line.getAs[Int](0)
val name2 = line.getAs[String](1)
val age2 = line.getAs[Int](2)
println(s"${id2} -- ${name2} -- ${age2}")
}
}
2.DataFrame与DataSet一般与 Spark ml 同时使用
3.DataFrame与DataSet均支持 SparkSql 的操作,比如select,groupby之类,
还能注册临时表/视窗,进行sql语句操作,如下:
df.createOrReplaceTempView("user")
spark.sql("select id,name,age from user").show()
4.DataFrame 与 DataSet 支持一些特别方便的保存方式,默认读取和输出的格式都是 parquet 格式
也可以保存成json、csv等格式... 如下:
【读取】
1.读取数据通用方式1:读取json格式
val df:DataFrame = spark.read.json("input/user.json")
或 val df:DataFrame = spark.read.format("json").load("input/user.json")
2.读取数据默认方式2:读取parquet格式
val df:DataFrame = spark.read.load("input/user.parquet")
【保存】
1.保存数据通用方式1:保存为指定格式,例如:json
df.write.format("csv").mode(SaveMode.Overwrite).save("outputcsv")
df.write.format("json").mode("append").save("outputjson")
2.保存数据默认方式2:保存为默认parquet格式,如果文件夹存在则覆盖
df.write.mode(SaveMode.Overwrite).save("outputparquet")
1.DataSet 和 DataFrame 拥有完全相同的成员函数,区别只是每一行的数据类型不同
2.DataFrame 也可以叫 DataSet[Row],每一行的类型是Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,
只能用上面提到的 getAS 方法或者三者共性中的第七条提到的模式匹配拿出特定字段
3.在DataSet中,每一行是什么类型是不一定的,在自定义了case class样例类之后可以很自由的获得每一行的信息
4.可以看出,DataSet在需要访问列中的某个字段时是非常方便的,然而,如果要写一些适配性很强的函数时,
如果使用DataSet,行的类型又不确定,可能是各种case class,无法实现适配,这时候用DataFrame即DataSet[Row]就能比较好的解决问题
【3.8】三者的相互转换
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>${spark.version}</version>
<scope>provided</scope>
</dependency>
object HelloWorld {
def main(args: Array[String]) {
//创建SparkSession
val spark = SparkSession
.builder()
.master("local[*]")
.appName("sparkDemo")
.config(new SparkConf())
.getOrCreate()
// For implicit conversions like converting RDDs to DataFrames
import spark.implicits._
val df = spark.read.json("input/user.json")
// Displays the content of the DataFrame to stdout
df.show()
df.filter($"age" > 21).show()
df.createOrReplaceTempView("user")
spark.sql("SELECT * FROM user where age > 21").show()
spark.stop()
}
}
1、从Spark数据源进行创建:
val df = spark.read.json("examples/src/main/resources/people.json")
2、从RDD进行转换:
val peopleRdd = sc.textFile("examples/src/main/resources/people.txt")
val peopleDF = peopleRdd.map(_.split(",")).map(paras => (paras(0),paras(1).trim().toInt)).toDF("name","age")
val df = spark.read.json("examples/src/main/resources/people.json")
df.show()
import spark.implicits._
df.printSchema()
df.select("name").show()
df.select($"name", $"age" + 1).show()
df.filter($"age" > 21).show()
df.groupBy("age").count().show()
df.createOrReplaceTempView("people")
val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
df.createGlobalTempView("people")
spark.sql("SELECT * FROM global_temp.people").show()
spark.newSession().sql("SELECT * FROM global_temp.people").show()
注意:临时表是Session范围内的,Session退出后,表就失效了。
如果想应用范围内有效,可以使用全局表。注意使用全局表时需要全路径访问,如:global_temp.people
Dataset是具有强类型的数据集合,需要提供对应的类型信息
import spark.implicits._
case class Person(name: String, age: Long)
val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()
val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
Spark SQL支持通过两种方式将存在的RDD转换为DataSet,转换的过程中需要让DataSet获取RDD中的Schema信息,主要有两种方式,一种是通过反射来获取RDD中的Schema信息。这种方式适合于列名已知的情况下。第二种是通过编程接口的方式将Schema信息应用于RDD,这种方式可以处理那种在运行时才能知道列的方式
①.通过反射获取Scheam
SparkSQL能够自动将包含有case类的RDD转换成DataSet,case类定义了table的结构,case类属性通过反射变成了表的列名。case类可以包含诸如Seqs或者Array等复杂的结构
import spark.implicits._
val peopleDF = spark.sparkContext
.textFile("examples/src/main/resources/people.txt")
.map(_.split(","))
.map(attributes => Person(attributes(0), attributes(1).trim.toInt))
.toDF()
peopleDF.createOrReplaceTempView("people")
val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")
teenagersDF.map(teenager => "Name: " + teenager(0)).show()
teenagersDF.map(teenager => "Name: " + teenager.getAs[String]("name")).show()
implicit val mapEncoder = org.apache.spark.sql.Encoders.kryo[Map[String, Any]]
teenagersDF.map(teenager => teenager.getValuesMap[Any](List("name", "age"))).collect()
②.通过编程设置Schema(StructType)
如果case类不能够提前定义,可以通过下面三个步骤定义一个DataFrame
【1】、创建一个多行结构的RDD;
【2】、创建用StructType来表示的行结构信息
【3】、通过SparkSession提供的createDataFrame方法来应用Schema
import spark.implicits._
import org.apache.spark.sql.types._
val peopleRDD = spark.sparkContext.textFile("examples/src/main/resources/people.txt")
val schema = StructType(Seq(
StructField("name",StringType,true),
StructField("age",StringType,true)
))
import org.apache.spark.sql._
val rowRDD = peopleRDD
.map(_.split(","))
.map(attributes => Row(attributes(0), attributes(1).trim))
val peopleDF = spark.createDataFrame(rowRDD, schema)
peopleDF.createOrReplaceTempView("people")
val results = spark.sql("SELECT name FROM people")
results.map(attributes => "Name: " + attributes().show()
通过spark.udf功能用户可以自定义函数
①.用户自定义UDF函数
val df = spark.read.json("examples/src/main/resources/people.json")
df.show()
spark.udf.register("addName", (x:String)=> "Name:"+x)
df.createOrReplaceTempView("people")
spark.sql("Select addName(name), age from people").show()
②.用户自定义聚合函数
强类型的Dataset和弱类型的DataFrame都提供了相关的聚合函数, 如 count(),countDistinct(),avg(),max(),min()。除此之外,用户可以设定自己的自定义聚合函数
object SparkSQL_UDAF1 {
def main(args: Array[String]): Unit = {
//创建SparkSQL环境对象
val spark = SparkSession
.builder()
.master("local[*]")
.appName("SparkSQL05_UDAF")
.config(new SparkConf())
.getOrCreate()
import spark.implicits._
//自定义聚合函数
val udaf = new MyAgeAvgFunction
//注册聚合函数
spark.udf.register("avgAge",udaf)
val df: DataFrame = spark.read.json("input/user.json")
df.createOrReplaceTempView("user")
spark.sql("select avgAge(age) from user").show()
//释放资源
spark.stop()
}
//自定义聚合函数(弱类型)
//1.继承UserDefinedAggregateFunction
//2.实现方法
class MyAgeAvgFunction extends UserDefinedAggregateFunction{
//函数输入的数据结构
override def inputSchema: StructType = new StructType().add("age",LongType)
//计算时的数据结构
override def bufferSchema: StructType = new StructType().add("sum",LongType).add("count",LongType)
//函数返回的数据类型
override def dataType: DataType = DoubleType
//函数是否稳定
override def deterministic: Boolean = true
//计算之前,缓冲区的初始化
override def initialize(buffer: MutableAggregationBuffer): Unit = {
//sum
buffer(0) = 0L
//count
buffer(1) = 0L
}
//根据查询结果更新缓冲区数据
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
//sum
buffer(0) = buffer.getLong(0) + input.getLong(0)
//count
buffer(1) = buffer.getLong(1) + 1
}
//将多个节点的缓冲区合并
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)
}
//计算最终结果
override def evaluate(buffer: Row): Any = {
buffer.getLong(0).toDouble / buffer.getLong(1)
}
}
}
object SparkSQL_UDAF2 {
def main(args: Array[String]): Unit = {
//创建SparkSQL环境对象
val spark = SparkSession
.builder()
.master("local[*]")
.appName("SparkSQL06_UDAF")
.config(new SparkConf())
.getOrCreate()
import spark.implicits._
//自定义聚合函数
val udaf = new MyAgeAvgFunction
//将聚合函数转换为查询列
val avgCol: TypedColumn[UserBean, Double] = udaf.toColumn.name("avgAge")
val df: DataFrame = spark.read.json("input/user.json")
val ds = df.as[UserBean]
//应用函数
ds.select(avgCol).show()
//释放资源
spark.stop()
}
case class UserBean(name: String, age: BigInt)
case class AvgBuffer(var sum: BigInt, var count: Int)
//自定义聚合函数(强类型)
//1.继承Aggregator,设置泛型
//2.实现方法
class MyAgeAvgFunction extends Aggregator[UserBean, AvgBuffer, Double] {
//初始化
override def zero: AvgBuffer = AvgBuffer(0, 0)
//聚合数据
override def reduce(b: AvgBuffer, a: UserBean): AvgBuffer = {
b.sum += a.age
b.count += 1
b
}
//缓冲区合并操作
override def merge(b1: AvgBuffer, b2: AvgBuffer): AvgBuffer = {
b1.sum += b2.sum
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
}
}