大数据技术之Spark SQL
一:Spark SQL的概述
- 定义:Spark SQL是Spark用来处理结构化数据的一个模块,它提供了2个编程对象:DataFrame和DataSet,并且作为分布式SQL查询引擎的作用。
- 特点:易整合、统一的数据访问方式、兼容Hive、标准的数据连接
- DtaFrame的定义:与RDD类似,DataFrame也是一个分布式的数据容器。然而Dataframe更像是一个数据库的二维表格,除了有数据以外,还记录着数据的结构信息,即schema
- DataSet的定义:DataSet是DtaFrame API的一个扩展,是Spark最新的数据抽象。
1) 是 Dataframe API 的一个扩展,是 Spark 最新的数据抽象。
2) 用户友好的 API 风格,既具有类型安全检查也具有 Dataframe 的查询优化特性。
3) Dataset 支持编解码器, 当需要访问非堆上的数据时可以避免反序列化整个对象,提高了
效率。
4) 样例类被用来在 Dataset 中定义数据的结构信息,样例类中每个属性的名称直接映射到
4 / 26 北京东燕郊开发区燕灵路方舟广场南侧 169 号 电话: 010-83868569
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 对
象和类对象之间的类比
二:Spark SQL 编程
在老版本中,SparkSQL 提供两种 SQL 查询起始点: 一个叫 SQLContext,用于 Spark 自己提供的 SQL 查询; 一个叫 HiveContext,用于连接 Hive 的查询。
SparkSession 是Spark最新的 SQL 查询起始点,实质上是 SQLContext 和 HiveContext 的组合,所以在 SQLContext 和 HiveContext 上可用的 API 在 SparkSession 上同样是可以使用的。SparkSession 内部封装了 sparkContext,所以计算实际上是由 sparkContext 完成的。
- DataFrame: 在 Spark SQL 中 SparkSession 是创建 DataFrame 和执行 SQL 的入口,创建 DataFrame 有三种方式: 通过 Spark 的数据源进行创建; 从一个存在的 RDD 进行转换; 还可以从 Hive Table 进行查询返回。
1)数据源创建
1. 查看Spark数据源进行创建的文件格式
spark.read
2. 读取json文件创建DataFrame
val df=spark.read.json("/opt/module/spark/examples/src/main/resources/people.json")
2)从RDD进行转换创建
3)从Hive Table进行查询返回创建
后续第三章着重介绍
- SQL的语法风格
1. 创建一个DataFrame
val df=spark.read.json("/opt/module/spark/examples/src/main/resources/people.json")
2.对 DataFrame 创建一个临时表
scala> df.createOrReplaceTempView("people")
注意: 临时表是 Session 范围内的, Session 退出后,表就失效了。如果想应用范围内有效,可以
使用全局表。注意使用全局表时需要全路径访问, 如: global_temp.people
3. 对于 DataFrame 创建一个全局表
scala> df.createGlobalTempView("people")
4. 通过 SQL 语句实现查询全表
scala> spark.sql("SELECT * FROM global_temp.people").show()
scala> spark.newSession().sql("SELECT * FROM global_temp.people").show()
- RDD转换为DataFrame
- 注意: 如果需要 RDD 与 DF 或者 DS 之间操作,那么都需要引入 import spark.implicits._ 【 spark
不是包名,而是 sparkSession 对象的名称】
前置条件: 导入隐式转换并创建一个 RDD 。
scala> import spark.implicits._
import spark.implicits._
scala> val peopleRDD = sc.textFile("examples/src/main/resources/people.txt")
peopleRDD: org.apache.spark.rdd.RDD[String] = examples/src/main/resources/people.txt MapPartitionsRDD[3] at
textFile at <console>:27
1)通过手动确定转换
scala> peopleRDD.map{x=>val para = x.split(",");(para(0),para(1).trim.toInt)}.toDF("name","age")
- DataFrame转换为RDD
- 直接调用rdd即可
1) 创建一个 DataFrame
scala> val df = spark.read.json("/opt/module/spark/examples/src/main/resources/people.json")
2)将 DataFrame 转换为 RDD
scala> val dfToRDD = df.rdd
三:DataSet:DataSet是具有强类型的数据集合,需要提供对应的类型信息
- 创建DataSet
1. 创建一个样例类
case class Person(nam: String,age:Long)
2. 创建 DataSet
val caseClassDS = Seq(Person("Andy", 32)).toDS()
- RDD转换为DataSet
SparkSQL 能够自动将包含有 case 类的 RDD 转换成 DataFrame, case 类定义了 table 的结构,
case 类属性通过反射变成了表的列名。
1. 创建一个 RDD
val peopleRDD = sc.textFile("examples/src/main/resources/people.txt")
2. 创建一个样例类
case class Person(name: String, age: Long)
3. 将 RDD 转化为 DataSet
scala> peopleRDD.map(line => {val para = line.split(",");Person(para(0),para(1).trim.toInt)}).toDS()
- DataSet转换为RDD:调用rdd方法即可
1) 创建一个 DataSet
scala> val DS = Seq(Person("Andy", 32)).toDS()
2)将 DataSet 转换为 RDD
scala> DS.rdd
- DataFrame互相转换为Data’’Set
1)创建一个 DateFrame
scala> val df = spark.read.json("examples/src/main/resources/people.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
2)创建一个样例类
scala> case class Person(name: String, age: Long)
defined class Person
3)将 DateFrame 转化为 DataSet
scala> df.as[Person]
res14: org.apache.spark.sql.Dataset[Person] = [age: bigint, name: string]
2. DataSet 转换为 DataFrame
1)创建一个样例类
scala> case class Person(name: String, age: Long)
defined class Person
2)创建 DataSet
scala> val ds = Seq(Person("Andy", 32)).toDS()
ds: org.apache.spark.sql.Dataset[Person] = [name: string, age: bigint]
3)将 DataSet 转化为 DataFrame
scala> val df = ds.toDF
总结:DataSet转换DataFrame的方法很简单,只需要把case class封装成ROW即可,导入隐式转换后调用toDF。DataFrame转DataSet也比较简单,只需要导入隐式转换后调用as【row】方法,将其具体数据类型标注清除即可
- 三者共性
1、 RDD、 DataFrame、 Dataset 全都是 spark 平台下的分布式弹性数据集,为处理超大型数据
提供便利
2、三者都有惰性机制,在进行创建、转换,如 map 方法时,不会立即执行,只有在遇到 Action
如 foreach 时,三者才会开始遍历运算。
3、三者都会根据 spark 的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存
溢出。
4、三者都有 partition 的概念
5、三者有许多共同的函数,如 filter,排序等
6、在对 DataFrame 和 Dataset 进行操作许多操作都需要这个包进行支持
import spark.implicits._
7、 DataFrame 和 Dataset 均可使用模式匹配获取各个字段的值和类型 - 三者区别
1.RDD:
1) RDD 一般和 spark mlib 同时使用
2) RDD 不支持 sparksql 操作
2.DataFrame:
1) 与 RDD 和 Dataset 不同, DataFrame 每一行的类型固定为 Row, 每一列的值没法直接访问, 只有通过解析才能获取各个字段的值,
2) DataFrame 与 Dataset 一般不与 spark mlib 同时使用
3) DataFrame 与 Dataset 均支持 sparksql 的操作,比如 select, groupby 之类,还能注册临时表/视窗,进行 sql 语句操作
4) DataFrame 与 Dataset 支持一些特别方便的保存方式,比如保存成 csv,可以带上表头,这样每一列的字段名一目了然
3.DataSet:
1) Dataset 和 DataFrame 拥有完全相同的成员函数,区别只是每一行的数据类型不同。
2) DataFrame 也可以叫 Dataset[Row],每一行的类型是 Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的 getAS 方法或者共性中的第七条提到的模式匹配拿出特定字段。 而 Dataset 中,每一行是什么类型是不一定的,在自定义了 case class之后可以很自由的获得每一行的信息
可以看出, Dataset 在需要访问列中的某个字段时是非常方便的,然而,如果要写一些适配性
很强的函数时,如果使用 Dataset,行的类型又不确定,可能是各种 case class,无法实现适配, 这时候用 DataFrame 即 Dataset[Row]就能比较好的解决问题 。 - IDEA创建SparkSQL以及用户自定义UDF
package com.ityouxin.sparkSql
import org.apache.spark.sql.SparkSession
object HelloWorld {
def main(args: Array[String]): Unit = {
//创建 SparkConf()并设置 App 名称
val spark = SparkSession
.builder()
.master("local[*]")
.appName("Spark SQL basic example")
.config("spark.some.config.option", "some-value")
.getOrCreate()
import spark.implicits._
val df = spark.read.json("datas/people.json")
df.show()
df.filter($"age" > 21).show()
df.createOrReplaceTempView("persons")
spark.sql("SELECT * FROM persons where age > 21").show()
spark.stop()
}
}
四:Spark SQL的数据源
- 通用加载/保存方法
- 手动指定选项
Spark SQL 的 DataFrame 接口支持多种数据源的操作。一个 DataFrame 可以进行 RDDs 方式的操作,也可以被注册为临时表。把 DataFrame 注册为临时表之后,就可以对该 DataFrame 执行SQL 查询。
Spark SQL 的默认数据源为 Parquet 格式。数据源为 Parquet 文件时, Spark SQL 可以方便的执行所有的操作。修改配置项 spark.sql.sources.default,可修改默认数据源格式。
当数据源格式不是 parquet 格式文件时,需要手动指定数据源的格式。数据源格式需要指定全名(例如: org.apache.spark.sql.parquet),如果数据源格式为内置格式,则只需要指定简称定json, parquet, jdbc, orc, libsvm, csv, text 来指定数据的格式。可以通过 SparkSession 提供的 read.load 方法用于通用加载数据,使用 write 和 save 保存数据。
val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
val sqlDF = spark.sql("SELECT * FROM parquet.`hdfs://hadoop102:9000/namesAndAges.parquet`")
- 文件保存选项
可以采用 SaveMode 执行存储操作, SaveMode 定义了对数据的处理模式。需要注意的是,这些保存模式不使用任何锁定,不是原子操作。此外,当使用 Overwrite 方式执行时,在输出新数据之前原数据就已经被删除。 SaveMode 详细介绍如下表: 略 - JSON
Spark SQL 能够自动推测 JSON 数据集的结构,并将它加载为一个 Dataset[Row]. 可以通过SparkSession.read.json()去加载一个 一个 JSON 文件。
注意: 这个 JSON 文件不是一个传统的 JSON 文件,每一行都得是一个 JSON 串。
val path = "examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)
peopleDF.printSchema()
- Parquet 文件
Parquet 是一种流行的列式存储格式,可以高效地存储具有嵌套字段的记录。 Parquet 格式经常在Hadoop 生态圈中被使用,它也支持 Spark SQL 的全部数据类型。 Spark SQL 提供了直接读取和存储 Parquet 格式文件的方法。
importing spark.implicits._
import spark.implicits._
val peopleDF = spark.read.json("examples/src/main/resources/people.json")
peopleDF.write.parquet("hdfs://hadoop102:9000/people.parquet")
val parquetFileDF = spark.read.parquet("hdfs:// hadoop102:9000/people.parquet")
parquetFileDF.createOrReplaceTempView("parquetFile")
val namesDF = spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")
namesDF.map(attributes => "Name: " + attributes(0)).show()
- JDBC
Spark SQL 可以通过 JDBC 从关系型数据库中读取数据的方式创建 DataFrame,通过对DataFrame 一系列的计算后,还可以将数据再写回关系型数据库中。
注意:需要将相关的数据库驱动放到 spark 的类路径下。
/load 默认的加载文件的格式由参数spark.sql.source.default决定
val df = spark.read.load("datas/users.parquet")
df.show()
// spark.sql("select * from parquet.'datas/users.parquet'").show()
//spark.sql("select * from json.'datas/people.json'").show()
//jdbc
spark.read.format("jdbc")
.option("url","jdbc:mysql://localhost:3306/day10")
.option("dbtable","user")
.option("user","root")
.option("password","123")
.load().show()
println("-------------------")
val option = new Properties
option.setProperty("user","root")
option.setProperty("password","123")
spark.read.jdbc("jdbc:mysql://localhost:3306/day10","user",option).show()
- Hive
Apach Hive 是 Hadoop 上的 SQL 引擎, Spark SQL 编译时可以包含 Hive 支持,也可以不包
含。包含 Hive 支持的 Spark SQL 可以支持 Hive 表访问、 UDF(用户自定义函数)以及 Hive 查询
语言(HiveQL/HQL)等。需要强调的一点是,如果要在 Spark SQL 中包含 Hive 的库,并不需要事
先安装 Hive。一般来说,最好还是在编译 Spark SQL 时引入 Hive 支持,这样就可以使用这些特
性了。如果你下载的是二进制版本的 Spark,它应该已经在编译时添加了 Hive 支持。