0 什么是 Spark SQL
Spark SQL 是 Spark 用于结构化数据(structured data)处理的 Spark 模块.
与基本的 Spark RDD API 不同, Spark SQL 的抽象数据类型为 Spark 提供了关于数据结构和正在执行的计算的更多信息.
在内部, Spark SQL 使用这些额外的信息去做一些额外的优化.
有多种方式与 Spark SQL 进行交互, 比如: SQL 和 Dataset API. 当计算结果的时候, 使用的是相同的执行引擎, 不依赖你正在使用哪种 API 或者语言.
这种统一也就意味着开发者可以很容易在不同的 API 之间进行切换, 这些 API 提供了最自然的方式来表达给定的转换.
我们已经学习了 Hive,它是将 Hive SQL 转换成 MapReduce 然后提交到集群上执行,大大简化了编写 MapReduc 的程序的复杂性。
由于 MapReduce 这种计算模型执行效率比较慢, 所以 Spark SQL 的应运而生,它是将 Spark SQL 转换成 RDD,然后提交到集群执行,执行效率非常快!
Spark SQL 它提供了2个编程抽象, 类似 Spark Core 中的 RDD
1)DataFrame
2)DataSet
1 Spark SQL 的特点
1.1 Integrated(易整合)
无缝的整合了 SQL 查询和 Spark 编程.
1.2 Uniform Data Access(统一的数据访问方式)
使用相同的方式连接不同的数据源.
1.3 Hive Integration(集成 Hive)
在已有的仓库上直接运行 SQL 或者 HiveQL
1.4 Standard Connectivity(标准的连接方式)
通过 JDBC 或者 ODBC 来连接
2什么是 DataFrame
与 RDD 类似,DataFrame 也是一个分布式数据容器。
然而DataFrame更像传统数据库的二维表格,除了数据以外,还记录数据的结构信息,即schema。
同时,与Hive类似,DataFrame也支持嵌套数据类型(struct、array和map)。
从 API 易用性的角度上看,DataFrame API提供的是一套高层的关系操作,比函数式的 RDD API 要更加友好,门槛更低。

3 什么是 DataSet
1) 是DataFrame API的一个扩展,是 SparkSQL 最新的数据抽象(1.6新增)。
2) 用户友好的API风格,既具有类型安全检查也具有DataFrame的查询优化特性。
3)Dataset支持编解码器,当需要访问非堆上的数据时可以避免反序列化整个对象,提高了效率。
4) 样例类被用来在DataSet中定义数据的结构信息,样例类中每个属性的名称直接映射到DataSet中的字段名称。
5)DataFrame是DataSet的特列,DataFrame=DataSet[Row] ,所以可以通过as方法将DataFrame转换为DataSet。Row是一个类型,跟Car、Person这些的类型一样,所有的表结构信息都用Row来表示。
6)DataSet是强类型的。比如可以有DataSet[Car],DataSet[Person].
7) DataFrame只是知道字段,但是不知道字段的类型,所以在执行这些操作的时候是没办法在编译的时候检查是否类型失败的,比如你可以对一个String进行减法操作,在执行的时候才报错,而DataSet不仅仅知道字段,而且知道字段类型,所以有更严格的错误检查。就跟JSON对象和类对象之间的类比。
4 spark SQL编程
4.1 SparkSession
SparkSession内部封装了SparkContext,所以计算实际上是由SparkContext完成的。
当我们使用 spark-shell 的时候, spark 会自动的创建一个叫做spark的SparkSession, 就像我们以前可以自动获取到一个sc来表示SparkContext

可看到当前spark.read支持的数据源

4.2创建 DataFrame
有了 SparkSession 之后, 通过 SparkSession有 3 种方式来创建DataFrame:
1通过 Spark 的数据源创建
2通过已知的 RDD 来创建
3通过查询一个 Hive 表来创建.
通过spark自带的文件为例子:
// 读取 json 文件
val df = spark.read.json("/root/software/spark-2.1.1-yarn/examples/src/main/resources/employees.json")
df.show
employees.json文件位于spark安装包的example/src/main/resource目录下

4.3 SQL语法风格
SQL 语法风格是指我们查询数据的时候使用 SQL 语句来查询.这种风格的查询必须要有临时视图或者全局视图来辅助
scala> val df2 = spark.read.json("/root/software/spark-2.1.1-yarn/examples/src/main/resources/people.json")
scala> df2.createOrReplaceTempView("people")
scala> spark.sql("select * from people").show

注意:
- 临时视图只能在当前 Session 有效, 在新的 Session 中无效.
- 可以创建全局视图. 访问全局视图需要全路径:如global_temp.xxx
scala> val df3 = spark.read.json("/root/software/spark-2.1.1-yarn/examples/src/main/resources/people.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
scala> df3.createGlobalTempView("people")
scala> spark.sql("select * from global_temp.people")
scala> res4.show
scala> spark.newSession.sql("select * from global_temp.people")
scala> res6.show

4.4 DSL风格
DataFrame提供一个特定领域语言(domain-specific language, DSL)去管理结构化的数据. 可以在 Scala, Java, Python 和 R 中使用 DSL。使用 DSL 语法风格不必去创建临时视图了.
scala> val df4 = spark.read.json("/root/software/spark-2.1.1-yarn/examples/src/main/resources/people.json")
scala> df4.printSchema

4.4.1 DSL查询
- 只查询name
df4.select($"name").show或df4.select($"name").show

- 查询name和age
df4.select($"name",$"age").show或df4.select("name","age").show

3) 查询name和age+1
df4.select($"name",$"age"+1).show

注意:
设计到运算的时候, 每列都必须使用$
- 查询age>20
df4.select($"name",$"age">20).show

5) 按age分组,查看数据条
df4.groupBy($“age”).count.show

4.5 RDD 和 DataFrame 的交互
RDD转DataFrame
涉及到RDD, DataFrame, DataSet之间的操作时, 需要导入:import spark.implicits._ 这里的spark不是包名, 而是表示SparkSession 的那个对象. 所以必须先创建SparkSession对象再导入. implicits是一个内部object
首先创建一个RDD
val rdd1 = sc.textFile("/root/software/spark-2.1.1-yarn/examples/src/main/resources/people.txt")
4.5.1手动转换
val rdd2 = rdd1.map(line => { val paras = line.split(", "); (paras(0), paras(1).toInt)})
// 转换为 DataFrame 的时候手动指定每个数据字段名
rdd2.toDF("name", "age").show

4.5.2通过样例类反射转换(最常用)
创建样例类
scala> case class People(name :String, age: Int)
使用样例把 RDD 转换成DataFrame
scala> val rdd2 = rdd1.map(line => { val paras = line.split(", "); People(paras(0), paras(1).toInt) })
scala> rdd2.toDF.show

4.5.3 通过 API 的方式转换(了解)
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types.{IntegerType, StringType, StructField, StructType}
import org.apache.spark.sql.{DataFrame, Row, SparkSession}
object DataFrameDemo {
def main(args: Array[String]): Unit = {
//1 获取sparksession对象
val spark = SparkSession.builder().appName("DataFrameDemo").master("local[2]").getOrCreate()
val sc: SparkContext = spark.sparkContext
val rdd1: RDD[(String, Int)] = sc.parallelize(Array(("lisi", 10), ("zs", 20), ("zhiling", 40)))
// 映射出来一个 RDD[Row], 因为 DataFrame其实就是 DataSet[Row]
val rdd2: RDD[Row] = rdd1.map(x => Row(x._1, x._2))
// 创建 StructType 类型
val types = StructType(Array(StructField("name", StringType), StructField("age", IntegerType)))
//将RDD转换为DataFrame
val df: DataFrame = spark.createDataFrame(rdd2, types)
df.show()
}
}

DataFrame转RDD
直接调用DataFrame的rdd方法就完成了从转换.
scala> val df = spark.read.json("/root/software/spark-2.1.1-yarn/examples/src/main/resources/people.json")
scala> val rdd = df.rdd
scala> rdd.collect

4.6 DataSet编程
DataSet 和 RDD 类似, 但是DataSet没有使用 Java 序列化或者 Kryo序列化, 而是使用一种专门的编码器去序列化对象, 然后在网络上处理或者传输.
虽然编码器和标准序列化都负责将对象转换成字节,但编码器是动态生成的代码,使用的格式允许Spark执行许多操作,如过滤、排序和哈希,而无需将字节反序列化回对象。
DataSet是具有强类型的数据集合,需要提供对应的类型信息。
4.6.1 创建Dataset
4.6.1.1 方式一
使用样例类的序列得到DataSet
scala> case class Person(name: String, age: Int)
// 为样例类创建一个编码器
scala> val ds = Seq(Person("lisi", 20), Person("zs", 21)).toDS
scala> ds.show

4.6.1.2 方式二
使用基本类型的序列得到 DataSet
// 基本类型的编码被自动创建. importing spark.implicits._
scala> val ds = Seq(1,2,3,4,5,6).toDS
scala> ds.show

4.6.2 DataSet和RDD交互
4.6.2.1 从RDD到DataSet
为 Spark SQL 设计的 Scala API 可以自动的把包含样例类的 RDD 转换成 DataSet。样例类定义了表结构: 样例类参数名通过反射被读到, 然后成为列名。样例类可以被嵌套, 也可以包含复杂类型: 像Seq或者Array。
val peopleRDD = sc.textFile("/root/software/spark-2.1.1-yarn/examples/src/main/resources/people.txt")
scala> case class Person(name: String, age: Long)
scala> peopleRDD.map(line => {val para = line.split(",");Person(para(0),para(1).trim.toInt)}).toDS.show

4.6.2.2 从DataSet到RDD
调用rdd方法即可
scala> val ds = Seq(Person("lisi", 40), Person("zs", 20)).toDS
// 把 ds 转换成 rdd
scala> val rdd = ds.rdd
scala> rdd.collect

4.7 DataFrame和DataSet交互
4.7.1 从 DataFrame到DataSet
scala> val df = spark.read.json("/root/software/spark-2.1.1-yarn/examples/src/main/resources/people.json")
scala> case class People(name: String, age: Long)
// DataFrame 转换成 DataSet
scala> val ds = df.as[People]

4.7.2从 DataSet到DataFrame
scala> case class Person(name: String, age: Long)
scala> val ds = Seq(Person("Andy", 32)).toDS()
scala> val df = ds.toDF
scala> df.show

4.8 RDD, DataFrame和 DataSet 之间转换关系

4.8.1三者的共性
- RDD、DataFrame、Dataset全都是 Spark 平台下的分布式弹性数据集,为处理超大型数据提供便利
- 三者都有惰性机制,在进行创建、转换,如map方法时,不会立即执行,只有在遇到Action如foreach时,三者才会开始遍历运算。
- 三者都会根据 Spark 的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出
- 三者都有partition的概念
- 三者有许多共同的函数,如map, filter,排序等
- 在对 DataFrame和Dataset进行操作许多操作都需要这个包进行支持 import spark.implicits._
4.8.2三者的区别
4.8.2.1 RDD
- RDD一般和spark mlib同时使用
- RDD不支持sparksql操作
4.8.2.2 DataFrame
- 与RDD和Dataset不同,DataFrame每一行的类型固定为Row,每一列的值没法直接访问,只有通过解析才能获取各个字段的值,
- DataFrame与DataSet一般不与 spark mlib 同时使用
- DataFrame与DataSet均支持 SparkSQL 的操作,比如select,groupby之类,还能注册临时表/视窗,进行 sql 语句操作
- DataFrame与DataSet支持一些特别方便的保存方式,比如保存成csv,可以带上表头,这样每一列的字段名一目了然(后面专门讲解)
4.8.2.3 DataSet
- Dataset和DataFrame拥有完全相同的成员函数,区别只是每一行的数据类型不同。 DataFrame其实就是DataSet的一个特例
- DataFrame也可以叫Dataset[Row],每一行的类型是Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的getAS方法或者共性中的第七条提到的模式匹配拿出特定字段。而Dataset中,每一行是什么类型是不一定的,在自定义了case class之后可以很自由的获得每一行的信息

SparkSQL是Spark用于结构化数据处理的模块,提供DataFrame和DatasetAPI,与RDD相比具备优化和易用性。它可以无缝整合SQL查询,统一访问不同数据源,并集成Hive。DataFrame是带schema的分布式数据集,而Dataset是强类型的,支持编解码提高效率。SparkSQL支持SQL语法和DSL,可与RDD相互转换,实现高效的数据处理。
2088





