第1章 SparkSQL 概述
Spark SQL 是 Spark 用于结构化数据(structured data)处理的 Spark 模块。
➢ 数据兼容方面
SparkSQL 不但兼容 Hive,还可以从 RDD、parquet 文件、JSON 文件中获取数据,未来版本甚至支持获取 RDBMS 数据以及 cassandra 等 NOSQL 数据;
➢ 性能优化方面
除了采取 In-Memory Columnar Storage、byte-code generation 等优化技术外、将会引进 Cost Model 对查询进行动态评估、获取最佳物理计划等等;
➢ 组件扩展方面
无论是 SQL 的语法解析器、分析器还是优化器都可以重新定义,进行扩展。
SparkSQL 特点
易整合,统一的数据访问,兼容 Hive,标准数据连接
DataFrame
DataFrame 与 RDD 的主要区别在于,前者带有 schema 元信息,即 DataFrame所表示的二维表数据集的每一列都带有名称和类型.。
Spark SQL 的查询优化器会进行逻辑查询计划优化。它是一个利用基于关系代数的等价变换,将高成本的操作替换为低成本操作的过程。
DataSet
是分布式数据集合。是 DataFrame API 的一个扩展,是 SparkSQL 最新的数据抽象
DataFrame 是 DataSet 的特例,DataFrame=DataSet[Row] ,所以可以通过 as 方法将
DataFrame 转换为 DataSet。
第2章 SparkSQL 核心编程
2.1 新的起点
Spark Core 中,如果想要执行应用程序,需要首先构建上下文环境对象 SparkContext。
SparkSession 是 Spark 最新的 SQL 查询起始点,实质上是 SQLContext 和 HiveContext的组合,所以在 SQLContex 和 HiveContext 上可用的 API 在 SparkSession 上同样是可以使用的。SparkSession 内部封装了 SparkContext,所以计算实际上是由 sparkContext 完成的。当我们使用 spark-shell 的时候, spark 框架会自动的创建一个名称叫做 spark 的 SparkSession 对象, 就像我们以前可以自动获取到一个 sc 来表示 SparkContext 对象一样。
2.2 DataFrame
➢ 查看 Spark 支持创建文件的数据源格式
scala> spark.read.
csv format jdbc json load option options orc parquet schema
table text textFile
2.2.1 SQL 语法
- 读取 JSON 文件创建 DataFrame
scala> val df = spark.read.json("data/user.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, username: string]
- 对 DataFrame 创建一个临时表
scala> df.createOrReplaceTempView("people")
- 通过 SQL 语句实现查询全表
scala> val sqlDF = spark.sql("SELECT * FROM people")
sqlDF: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
- 结果展示
scala> sqlDF.show
- 对于 DataFrame 创建一个全局表
scala> df.createGlobalTempView("people")
- 通过 SQL 语句实现查询全表
scala> spark.sql("SELECT * FROM global_temp.people").show()
or
scala> spark.newSession().sql("SELECT * FROM global_temp.people").show()
2.2.2 DSL 语法
特定领域语言(domain-specific language, DSL)管理结构化的数据。
- 创建一个 DataFrame
scala> val df = spark.read.json("data/user.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
- 查看 DataFrame 的 Schema 信息
scala> df.printSchema
- 只查看"username"列数据,
scala> df.select("username").show()
- 查看"username"列数据以及"age+1"数据
注意:涉及到运算的时候, 每列都必须使用$, 或者采用引号表达式:单引号+字段名
// 使用$版
scala> df.select($"username",$"age" + 1).show
// 使用‘版
scala> df.select('username, 'age + 1).show()
// 别名
scala> df.select('username, 'age + 1 as "newage").show()
- 查看"age"大于"30"的数据
scala> df.filter($"age">30).show
- 按照"age"分组,查看数据条数
scala> df.groupBy("age").count.show
2.2.3 RDD,DataFrame 互转
RDD 转换为 DataFrame :.toDF
scala> val idRDD = sc.textFile("data/id.txt")
scala> idRDD.toDF("id").show
**DataFrame 转换为 RDD:.rdd **
// RDD转DataFrame
scala> val df = sc.makeRDD(List(("zhangsan",30), ("lisi",40))).map(t=>User(t._1, t._2)).toDF
df: org.apache.spark.sql.DataFrame = [name: string, age: int]
// DataFrame转RDD
scala> val rdd = df.rdd
rdd: org.apache.spark.rdd.RDD[org.apache.spark.sql.Row] = MapPartitionsRDD[46] at rdd at <console>:25
2.3 DataSet
2.3.1 创建 DataSet
1) 使用样例类序列创建 DataSet
// 定义类Person
scala> case class Person(name: String, age: Long)
// Person类序列toDS创建 DataSet
scala> val caseClassDS = Seq(Person("zhangsan",2)).toDS()
2) 使用基本类型的序列创建 DataSet
scala> val ds = Seq(1,2,3,4,5).toDS
2.3.2 RDD、DataSet 互转
// 定义类User
scala> case class User(name:String, age:Int)
// rdd转DataSet
scala> sc.makeRDD(List(("zhangsan",30), ("lisi",49))).map(t=>User(t._1,
t._2)).toDS
//DataSet转rdd
scala> val rdd = res11.rdd
2.3.3 DataFrame 和 DataSet 互转
// 定义类User
scala> case class User(name:String, age:Int)
scala> val df = sc.makeRDD(List(("zhangsan",30),
("lisi",49))).toDF("name","age")
// DataFrame转DataSet
scala> val ds = df.as[User]
// DataSet 转 DataFrame
scala> val df = ds.toDF
2.5 RDD、DataFrame、DataSet 三者的关系
—— | RDD | DataFrame | Dataset |
---|---|---|---|
产生版本 | Spark1.0 | Spark1.3 | Spark1.6 |
区别 | 一般和spark mllib同时使用, 不支持 sparksql 操作 | 一般不与 spark mllib 同时使用, 一行的类型固定为 Row,每一列的值没法直接访问, 与 DataSet 均支持 SparkSQL 的操作, 还能注册临时表/视窗,进行 sql 语句操作, 与 DataSet 支持一些特别方便的保存方式 | 是 DataSet 的一个特例, 也可以叫 Dataset[Row] 每一行的类型是 Row, 不解析 |
2.6 IDEA 开发 SparkSQL
2.6.1 添加依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.12</artifactId>
<version>3.0.0</version>
</dependency>
2.6.2 代码实现
object SparkSQL01_Demo {
def main(args: Array[String]): Unit = {
// 1.创建上下文环境配置对象
val conf: SparkConf = new
SparkConf().setMaster("local[*]").setAppName("SparkSQL01_Demo")
// 2.创建 SparkSession 对象
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
//RDD=>DataFrame=>DataSet 转换需要引入隐式转换规则,否则无法转换
//spark 不是包名,是上下文环境对象名
import spark.implicits._
// json=>DataFrame
//读取 json 文件 创建 DataFrame {"username": "lisi","age": 18}
val df: DataFrame = spark.read.json("input/test.json")
//df.show()
//SQL 风格语法
df.createOrReplaceTempView("user")
//spark.sql("select avg(age) from user").show
//DSL 风格语法
//df.select("username","age").show()
//*****RDD=>DataFrame=>DataSet*****
//RDD
val rdd1: RDD[(Int, String, Int)] =
spark.sparkContext.makeRDD(List((1,"zhangsan",30),(2,"lisi",28),(3,"wangwu",
20)))
//RDD=>DataFrame
val df1: DataFrame = rdd1.toDF("id","name","age")
//df1.show()
//DataFrame=>DataSet
val ds1: Dataset[User] = df1.as[User]
//ds1.show()
//*****DataSet=>DataFrame=>RDD*****
//DataSet=>DataFrame
val df2: DataFrame = ds1.toDF()
//RDD 返回的 RDD 类型为 Row,里面提供的 getXXX 方法可以获取字段值,类似 jdbc 处理结果集,但是索引从 0 开始
//DataFrame=>RDD
val rdd2: RDD[Row] = df2.rdd
//rdd2.foreach(a=>println(a.getString(1)))
//*****RDD=>DataSet*****
rdd1.map{
case (id,name,age)=>User(id,name,age)
}.toDS()
//*****DataSet=>=>RDD*****
ds1.rdd
//释放资源
spark.stop()
}
}
case class User(id:Int,name:String,age:Int)
2.7 用户自定义函数
2.7.1 UDF
- 创建 DataFrame
scala> val df = spark.read.json("data/user.json")
- 注册 UDF
scala> spark.udf.register("addName",(x:String)=> "Name:"+x)
- 创建临时表
scala> df.createOrReplaceTempView("people")
- 应用 UDF
scala> spark.sql("Select addName(name),age from people").show()
2.7.2 UDAF
强类型的 Dataset 和弱类型的 DataFrame 都提供了相关的聚合函数, 如 count(),countDistinct(),avg(),max(),min()。
2.8 数据的加载和保存
2.8.1 通用的加载和保存方式
加载
spark.read.load 是加载数据的通用方法
scala> spark.read.
csv format jdbc json load option options orc parquet schema
table text textFile
//如果读取不同格式的数据,可以对不同的数据格式进行设定
scala> spark.read.format("…")[.option("…")].load("…")
//也可以直接在文件上进行查询: 文件格式.`文件路径`
scala>spark.sql("select * from json.`/opt/module/data/user.json`").show
保存数据
df.write.save 是保存数据的通用方法
scala>df.write.
scala>df.write.format("…")[.option("…")].save("…")
➢ format(“…”):指定保存的数据类型,包括"csv"、“jdbc”、“json”、“orc”、“parquet"和 “textFile”。
➢ save (”…“):在"csv”、“orc”、“parquet"和"textFile"格式下需要传入保存数据的路径。
➢ option(”…"):在"jdbc"格式下需要传入 JDBC 相应参数,url、user、password 和 dbtable 保存操作可以使用 SaveMode, 用来指明如何处理数据,使用 mode()方法来设置。
Scala/Java | Any Language | Meaning |
---|---|---|
SaveMode.ErrorIfExists(default) | “error”(default) | 如果文件已经存在则抛出异常 |
SaveMode.Append | “append” | 如果文件已经存在则追加 |
SaveMode.Overwrite | “overwrite” | 如果文件已经存在则覆盖 |
SaveMode.Ignore | “ignore” | 如果文件已经存在则忽略 |
2.8.2 Parquet
- 加载数据
scala> val df = spark.read.load("examples/src/main/resources/users.parquet")
scala> df.show
- 保存数据
scala> var df = spark.read.json("/opt/module/data/input/people.json")
//保存为 parquet 格式
scala> df.write.mode("append").save("/opt/module/data/output")
2.8.3 JSON
1)导入隐式转换
import spark.implicits._
2)加载 JSON 文件
val path = "/opt/module/spark-local/people.json"
val peopleDF = spark.read.json(path)
3)创建临时表
peopleDF.createOrReplaceTempView("people")
4)数据查询
val teenagerNamesDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19")
teenagerNamesDF.show()
2.8.4 CSV
spark.read.format("csv").option("sep", ";").option("inferSchema", "true").option("header", "true").load("data/user.csv")
2.8.5 MySQL
1)导入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
2)读取数据
val conf: SparkConf = new
SparkConf().setMaster("local[*]").setAppName("SparkSQL")
//创建 SparkSession 对象
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
import spark.implicits._
//方式 1:通用的 load 方法读取
spark.read.format("jdbc")
.option("url", "jdbc:mysql://linux1:3306/spark-sql")
.option("driver", "com.mysql.jdbc.Driver")
.option("user", "root")
.option("password", "123123")
.option("dbtable", "user")
.load().show
//方式 2:通用的 load 方法读取 参数另一种形式
spark.read.format("jdbc")
.options(Map("url"->"jdbc:mysql://linux1:3306/spark-sql?user=root&password=
123123",
"dbtable"->"user","driver"->"com.mysql.jdbc.Driver")).load().show
//方式 3:使用 jdbc 方法读取
val props: Properties = new Properties()
props.setProperty("user", "root")
props.setProperty("password", "123123")
val df: DataFrame = spark.read.jdbc("jdbc:mysql://linux1:3306/spark-sql",
"user", props)
df.show
//释放资源
spark.stop()
3)写入数据
case class User2(name: String, age: Long)
。。。
val conf: SparkConf = new
SparkConf().setMaster("local[*]").setAppName("SparkSQL")
//创建 SparkSession 对象
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
import spark.implicits._
val rdd: RDD[User2] = spark.sparkContext.makeRDD(List(User2("lisi", 20),
User2("zs", 30)))
val ds: Dataset[User2] = rdd.toDS
//方式 1:通用的方式 format 指定写出类型
ds.write
.format("jdbc")
.option("url", "jdbc:mysql://linux1:3306/spark-sql")
.option("user", "root")
.option("password", "123123")
.option("dbtable", "user")
.mode(SaveMode.Append)
.save()
//方式 2:通过 jdbc 方法
val props: Properties = new Properties()
props.setProperty("user", "root")
props.setProperty("password", "123123")
ds.write.mode(SaveMode.Append).jdbc("jdbc:mysql://linux1:3306/spark-sql",
"user", props)
//释放资源
spark.stop()
2.8.6 Hive
代码操作 Hive
1)导入依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-hive_2.12</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-exec</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
2)将 hive-site.xml 文件拷贝到项目的 resources 目录中,代码实现
//创建 SparkSession
val spark: SparkSession = SparkSession
.builder()
.enableHiveSupport()
.master("local[*]")
.appName("sql")
.getOrCreate()