SparkSql-Datasets和DataFrames

本文介绍了Spark SQL的核心概念,包括Datasets、DataFrames和SparkSession的使用。详细阐述了如何创建和操作DataFrames,以及执行SQL查询。还探讨了Datasets与RDDs之间的交互和异同,强调了Datasets的类型安全和优化执行引擎优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SparkSql顾名思义就是可以执行sql查询,同样也可以用于从hive查询数据。

Datasets

Datasets分布式数据集。spark 1.6引入,提供了RDD的优点(强类型、强大的lambda函数)和Spark-SQL优化了的执行引擎。它可由JVM对象创建,然后使用函数式转换进行修改,比如map、flatmap、filter等。

DataFrames

DataFrames列已命名的Dataset。相当于关系型数据库的表,R/Python的data frame,但底层做了更丰富的优化。它可从结构化数据文件、hive表、其他数据库、RDD创建。在API层面,DataFrame表现为多个Row的Dataset,比如在scala API里,DataFrame 类型为 Dataset[Row],而在Java里则是Dataset<Row>。

Spark-SQL 编程入口 SparkSession
// 通过SparkSession.builder()构建
val spark = SparkSession
  .builder()
  .appName("Spark SQL basic example")
  .config("spark.some.config.option", "some-value")
  .getOrCreate()

// 隐式转换(RDDs -> DataFrames)
import spark.implicits._

SparkSession 在Spark 2.0以后内建了Hive,包括使用HiveQL进行查询,访问Hive 自定义函数,读取Hive表等,使用这些特性不需要另搭一套Hive环境。

Spark-SQL创建DataFrames

使用SparkSession,应用程序可从已知的RDD、Hive表、其他spark数据源创建。

// 使用JSON文件创建
val df = spark.read.json("examples/src/main/resources/people.json")
Spark-SQL DSL查询及操作(DataFrame操作,又叫无类型的Dataset操作)
// 为了使用 $ 语法
import spark.implicits._
// 打印树形结构
df.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)

// 查询 "name" 列
df.select("name").show()
// +-------+
// |   name|
// +-------+
// |Michael|
// |   Andy|
// | Justin|
// +-------+

// 查询每一条记录, 但 age 加 1
df.select($"name", $"age" + 1).show()
// +-------+---------+
// |   name|(age + 1)|
// +-------+---------+
// |Michael|     null|
// |   Andy|       31|
// | Justin|       20|
// +-------+---------+

// 查询 age > 21 的记录
df.filter($"age" > 21).show()
// +---+----+
// |age|name|
// +---+----+
// | 30|Andy|
// +---+----+

// 按age分组统计个数
df.groupBy("age").count().show()
// +----+-----+
// | age|count|
// +----+-----+
// |  19|    1|
// |null|    1|
// |  30|    1|
// +----+-----+

更多操作:Dataset操作
更多函数式操作:DataFrame函数操作

Spark-SQL 执行sql查询

通过SparkSession 调用sql查询,返回结果是另一个DataFrame,可认为是原DataFrame的子集。

// 把这个DataFrame 注册为一个 SQL 临时视图
df.createOrReplaceTempView("people")

val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
Spark-SQL全局临时视图

上例的临时视图,session范围内有效,当session退出时销毁。如果想建立一个跨多个session共享的临时视图(只有Spark应用程序退出时才销毁),可用createGlobalTempView,此视图跟系统保留数据库global_temp关联,并通过引用这个库才能使用。

// 把这个DataFrame 注册为一个全局临时视图
df.createGlobalTempView("people")

// 通过引用 `global_temp`来使用
spark.sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

// 跨session使用
spark.newSession().sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
Spark-SQL创建Datasets

Datasets与RDDs类似,但它不使用java serialization或Kryo来序列化对象,而是使用特殊的编码器来序列化对象。我们知道对象序列化以后是字节数组,而这种编码器支持代码动态生成,使用了一个特殊格式来允许spark执行多种操作(如filter、sort、hash)时无需把字节反序列化为对象。

case class Person(name: String, age: Long)

// 为样例类创建编码器
val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()
// +----+---+
// |name|age|
// +----+---+
// |Andy| 32|
// +----+---+

// 大多数通用类型的编码器由导入 spark.implicits._ 提供
val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)

// 通过一个类即可把DataFrames 转化成Dataset by providing a class
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
Spark-SQL与RDDs交互

Spark-SQL将现有RDDs转换为Datasets有两种方式:一种通过反射推出结构类型,一种通过编程接口定义结构类型

1.通过反射推出结构类型
import spark.implicits._

// 从一个文本文件创建RDD并转换为一个Dataframe
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")

// 运行sql
val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")

// 每一行的列可以通过索引访问
teenagersDF.map(teenager => "Name: " + teenager(0)).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+

// 或者通过一个列名
teenagersDF.map(teenager => "Name: " + teenager.getAs[String]("name")).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+

// 没有事先定义Dataset[Map[K,V]]的编码器, 现在定义它
implicit val mapEncoder = org.apache.spark.sql.Encoders.kryo[Map[String, Any]]
// 原始类型、样例类也可以像这样定义:
// implicit val stringIntMapEncoder: Encoder[Map[String, Any]] = ExpressionEncoder()

// row.getValuesMap[T] 将一行的多列一次转为Map[String, T]
teenagersDF.map(teenager => teenager.getValuesMap[Any](List("name", "age"))).collect()
// Array(Map("name" -> "Justin", "age" -> 19))
2.通过编程接口定义结构类型
import org.apache.spark.sql.types._

// 创建一个RDD
val peopleRDD = spark.sparkContext.textFile("examples/src/main/resources/people.txt")

// 将结构编成一个字串
val schemaString = "name age"

// 将结构字串变成结构类型对象
val fields = schemaString.split(" ")
  .map(fieldName => StructField(fieldName, StringType, nullable = true))
val schema = StructType(fields)

// 将记录转换为元素为Row的RDD
val rowRDD = peopleRDD
  .map(_.split(","))
  .map(attributes => Row(attributes(0), attributes(1).trim))

// 使用结构类型对象把这个RDD转换成DataFrame
val peopleDF = spark.createDataFrame(rowRDD, schema)

// 创建临时视图
peopleDF.createOrReplaceTempView("people")

// 执行sql
val results = spark.sql("SELECT name FROM people")

// results是一个DataFrame并且支持所有常用的RDD操作
// 每一行的列都可以通过索引或列名访问
results.map(attributes => "Name: " + attributes(0)).show()
// +-------------+
// |        value|
// +-------------+
// |Name: Michael|
// |   Name: Andy|
// | Name: Justin|
// +-------------+
RDD DataFrame Dataset异同

出生版本
RDD:Spark1.0
DataFrame:Spark1.3
Dataset:Spark1.6

Dataset是对RDD在sql特性上的封装,RDD不能执行sql查询,而DataSet可以。
DataFrame是Dataset的特例,DataFrame == Dataset[Row],DataFrame支持所有Dataset操作,DataFrame每一行是固定Row的类型,只能通过索引或列名访问列值,而Dataset每一行都是明确的类型,访问比较方便。如果行的类型不确定时,使用DataFrame比较合适。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值