SparkSQL 核心编程

1. SparkSession

1.1 新的起点

老版本中,Spark SQL 提供两种 SQL 查询的起点,一个为 SQLContext,用于 Spark 为自己提供的 SQL 查询;另一个为 HiveContext,用于连接 Hive 查询,SparkSession 是 SparkSQL 查询的新起点。实际上是 SQLContext、HiveContext 的组合,所以在 SQLContext 和 HiveContext 上可用的 API 在 SparkSession 上同样是可以使用的。SparkSession 内部封装 SparkContext,所以计算实际上是由 SparkContext 完成的。

如果留心观察,在命令行中启动 Spark 后就可以看到 SparkSession:

image-20211222104043044

1.2 IDEA 中创建 SparkSession

版本说明:

Scala:2.12.15

Spark:3.1.2

Spark Core:2.12

Spark SQL:2.12

1.2.1 添加SparkSQL依赖

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-sql_2.12</artifactId>
    <version>3.1.2</version>
</dependency>

1.2.2 为项目添加 Scala SDK

参考:Scala 初识+环境搭建

1.2.3 创建程序入口

SparkSession.builder 用于创建一个SparkSession。

val spark = SparkSession.builder()
  .appName("CreateSession")
  .master("local[2]")
  .getOrCreate()

将 Session 对象命名为 spark,原因可看上文截图中的 Session 别名。

SparkContext 别名为 sc,SparkSession别名为 spark

在后期使用中,会用到RDD,所以可添加隐式转换的包,用于将 DataFrame 转换为 RDD,使 DataFrame 能够使用 RDD 中的方法。

import spark.implicits._

这里的 spark 为上面创建出来的对象。

2. DataFrame

2.1 创建 DataFrame

在 SparkSQL 中,SparkSession 是创建 DataFrame 和执行 SQL 的入口。

创建 DataFrame 有三种方式:

  • 通过 Spark 数据源进行创建
  • 通过一个存在的 RDD 进行转换
  • 使用 spark.createDataFrame 创建
  • 通过 Hive Table 进行查询返回

准备数据 employees.json

{"name":"Michael", "salary":3000}
{"name":"Andy", "salary":4500}
{"name":"Justin", "salary":3500}
{"name":"Berta", "salary":4000}

2.1.1 通过 Spark 数据源进行创建

val spark = SparkSession.builder()
.appName("DataFrames")
.master("local[2]")
.getOrCreate()

// 创建 DataFrame ==========================================
// 方式一: 通过 Spark 数据源进行创建
val df = spark.read.json("employees.json")
df.show()

image-20211222114933709

2.1.2 通过一个存在的 RDD 进行转换

val spark = SparkSession.builder()
    .appName("DataFrames")
    .master("local[2]")
    .getOrCreate()
import spark.implicits._

// 创建 DataFrame ==========================================
// 方式二: 通过一个存在的 RDD 进行转换
val sc = SparkContext.getOrCreate()
val rdd = sc.textFile("employees.json")
val df = rdd.map(_.split("\""))
    .map(items => (items(3), items(6).substring(1, items(6).length - 1)))
    .toDF("name", "salary")
df.show()

image-20211222122433046

2.1.3 使用 spark.createDataFrame 创建

DataFrame在Spark1.6之后没有保留单独的类,而是将Dataset[Row]作为DataFrame类型的实现。

2.1.3.1 源码窥探(DF结构)

1️⃣ createDataFrame 方法具有多种重载

image-20211222183248177

2️⃣ 进入到其源码包 SparkSession 种,我们可找打 SparkSQL 对应的的方法:

  • rowRDD: RDD[Row]:Row泛型的RDD
  • schema: StructType:结构类型

image-20211222183649985

3️⃣ 进入到 Row 中,可以发现 Row 是一个特质,DF存储数据时,需要使用Row的实现类:Generic(通用类)、Aggregation(聚合,用于HDFS),不妨先看第一个 GenericRow

image-20211222184050248

4️⃣ 在该实现类中,包含一个成员属性,类型为 Array[Any],由于表中每一行包含不同类型的值,所以使用 Array[Any] 来保存各种类型的值,而我们知道 DF 不仅包含了字段的值,还包含字段的类型,显然不能使用该实现类!

image-20211222184450036

5️⃣ 继续向下,本类中包含了 GenericRow 的实现类,名为 GenericRowWithSchema,

image-20211222184804379

6️⃣ 在 GenericRowWithSchema 类中不仅包含字段的值,还具有一个成员变量,类型为 StructType, StructType 中包含一个成员属性fields类型是Array[StructField],可用于保存每行所有字段的结构信息,StructType 类如下:

image-20211222185101605

7️⃣ StructField 类具体定义了字段的名称及对应的数据类型,所及实际使用就是该类,如下:

image-20211222185352638

8️⃣ 所以我们可以看到,在 GenericRowWithSchema 中,成员变量为两个数组,一个用于保存数据内容,另一个用于保存字段的名称和属性信息,两个数组对应如下:

Array[Any]        (            "JOEl",                         18)
Array[StructField](StructField("name",StringType),StructField("age",IntegerType)    )

🌟 DF结构表述:

DataFrame 中,包含了数据的内容及数据的结构信息(字段名称、字段类型),DataFrame 就是 DataSet 的一个特例,其泛型为 Row 类型。Row 是一个特质,该特质具有两个特质,一个是 GenericRow ,它的成员变量 values 是一个 Any 泛型的数组,用来保存每一行当中的值;由于 Any 不能保存列的名称和列的类型,所以还有一个 GenericRowWithSchema,其包含一个 StructType 泛型的成员变量 schema,StructType 是 StructField 泛型的数组。StructField 类中包含了四个属性,其中主要的两个属性:name、datatype。两个数组对应后,values数组用来保存那一列的值,fileds 数组用 StructField 保存那一列的字段名称和属性,从而实现映射为别结构!

2.1.3.2 创建 DataFrame
val rdd: RDD[(String, Int)] = sc.makeRDD(List(
    ("Tom", 18),
    ("Jack", 28)
))
val rdd1: RDD[Row] = rdd.map(t => {
    Row(t._2, t._1)
})

val frame = spark.createDataFrame(
    rdd1,  // 用于保存每列数据的数组
    StructType(   
        Array[StructField](  // 用于保存字段名、字段类型的数组
            StructField("age", IntegerType),
            StructField("name", StringType)
        )
    )
)

frame.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDMzev9b-1640171892837)(https://cdn.jsdelivr.net/gh/JOEL-T99/Pic//img/image-20211222191509112.png)]

列的顺序按照字段名的字段顺序排列!

🌈 protected[xxx] 标识其修饰的类或属性可以在某包下被访问!

2.2 DataFrame 常用操作

2.2.1 SQL 语法风格

SQL 语法风格指的是查询数据时使用 SQL 语句来查询,这个风格相当于查询一张表,但 DataFrame 只是一种数据抽象,所以需要创建临时视图或全局视图来辅助。

全局视图相当于广播变量,将会存储在 Executor 中的 Cache 中,所有 Task 共用一份数据。

val spark = SparkSession.builder()
    .appName("DataFrames")
    .master("local[2]")
    .getOrCreate()

val df = spark.read.json("employees.json")
// 创建或替换临时视图
df.createOrReplaceTempView("employees")
// 使用sql语句查询
val sqlDF = spark.sql("select * from employees where salary > 3500")
// 显示数据
sqlDF.show()

image-20211222131419488


val df = spark.read.json("employees.json")
// 创建全局视图
df.createOrReplaceGlobalTempView("employees")
// 使用sql语句查询,当使用全局表时,需要使用 global_temp.表名
val sqlDF = spark.sql("select * from global_temp.employees where salary > 3500")
// 显示数据
sqlDF.show()

image-20211222131714324

2.2.2 DSL语法风格

DSL(Domain Specific Language):DataFrame 提供的一种领域特定语言去管理结构化的数据,使用 DSL 语法风格就不用创建视图了。

val df = spark.read.json("employees.json")
df
  .select("name", "salary")
  .where("salary > 3500")
  .show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ALvfP92-1640171892840)(https://cdn.jsdelivr.net/gh/JOEL-T99/Pic//img/image-20211222132342042.png)]


涉及到运算时,列名前需要使用 $ 标识,或使用单引号。(一用则全用)

// 查询时运算一
df
    .select($"name", $"salary" + 100 as "salary+100")
    .where("salary > 3500")
    .show()

image-20211222132829466

//  查询时运算二
df
    .select('name, 'salary + 100 as "salary+100")
    .where("salary > 3500")
    .show()

image-20211222132931547

2.3 DataFrame、RDD 互操作

在 IDEA 开发中,如果需要 RDD、DataFrame、DataSet 之间的互操作,需要引入上文提到的 import spark.implicits._

spark 为 SparkSession 对象!

spark 不能使用 var 修饰!因为 Scala 只支持 val 修饰的对象引入

2.3.1 DataFrame 转为 RDD

DataFrame 是对 RDD 的封装,所以可以直接获取内部的 RDD。

val df = spark.read.json("employees.json")
val rdd = df.rdd
rdd.foreach(println)

image-20211222134817399

得到的 RDD 泛型为 Row,解析后获取的字段会自动转型

val emp = rdd.collect
println(emp(0)(0).getClass)
println(emp(0)(1).getClass)
/*
输出:
class java.lang.String
class java.lang.Long
*/

2.3.2 RDD 转为 DataFrame

RDD 转为 DataFrame 时,一般通过案例类作为泛型。

case class employees(name: String, scala: Int)
val rdd = sc.makeRDD(List(("Michael",3000),("Andy",4500),("Justin",3500),("Berta",4000)))
val df = rdd
    .map(row => employees(row._1, row._2))
    .toDF()
df.show()

image-20211222140807651

3. DataSet

DataSet 是具有强类型的数据集合,需要提供对应的类型信息。

3.1 创建 DataSet

创建 DataSet 有三种方式:

  • 使用基本类型的序列创建 DataSet
  • 使用样例类的序列创建 DataSet
  • 读取文件创建 DataSet

3.1.1 使用基本类型的序列创建 DataSet

val ds = Seq(1,2,3,4,5).toDS()
ds.map(_+1).show()

image-20211222142416610

3.1.2 使用样例类的序列创建 DataSet

case class employees(name: String, salary: Int)
val ds = Seq(employees("Michael", 3000),
  employees("Andy", 4500),
  employees("Justin", 3500),
  employees("Berta", 4000)).toDS()
ds.map(_.salary + 100).show()

image-20211222143044334

3.1.3 读取文件创建 DataSet

val ds = spark.read.json("employees.json").as[employees]
ds.show()

image-20211222143445834

3.2 DataSet、RDD 互操作

3.2.1 DataSet 转为 RDD

DataSet 也是对 RDD 的封装,所以可以直接获取内部的 RDD。

val ds = spark.read.json("employees.json").as[employees]
val rdd = ds.rdd
rdd.foreach(println)

image-20211222145148820

3.2.2 RDD 转为 DataSet

SparkSQL 能够自动将包含有 case 类的 RDD 转换成 DataSet,case 类定义了 table 的结构,case 类属性通过反射变成了表的列名。Case 类可以包含诸如 Seq 或者 Array 等复杂的结构。

val rdd = sc.makeRDD(List(("Michael",3000),("Andy",4500),("Justin",3500),("Berta",4000)))
val ds = rdd
  .map(row => employees(row._1, row._2))
  .toDS()
ds.show()

3.3 DataSet、DataFrame 互操作

DataFrame 其实是DataSet 的特例,所以它们之间是可以互相转换的。

3.3.1 DataSet 转为 DataFrame

val ds = spark.read.json("employees.json").as[employees]
val df = ds.toDF()
df.show()

image-20211222144837228

3.3.2 DataFrame 转为 DataSet

val df = spark.read.json("employees.json")
val ds = df.as[employees]
ds.show()

image-20211222144959075

4. 类型之间的转换总结

DataFrame 转为 RDD:

val rdd = df.rdd

DataSet 转为 RDD:

val rdd = ds.rdd

RDD 转为 DataFrame:

// 无案例类的情况下
import spark.implicits._
val df = rdd
	.map(row => (row._1, row._2))
	.toDF("col1", "col2")
// 有案例类的情况下
case class employees(name: String, scalary: Int)
import spark.implicits._
val df = rdd
    .map(row => employees(row._1, row._2))
    .toDF()

RDD 转为 DataSet:

import spark.implicits._
val ds = rdd
    .map(row => employees(row._1, row._2))
    .toDS()

DataFrame 转为 DataSet:

case class employees(name: String, scalary: Int)
import spark.implicits._
val ds = df.as[employees]

DataSet 转为 DataFrame:

import spark.implicits._
val df = ds.toDF()

image-20211222151254802

 


❤️ END ❤️
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JOEL-T99

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值