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比较合适。