Spark SQL详解入门!

第1章 Spark SQL概述

1.1 什么是Spark SQL

Spark SQL是Spark用于结构化数据(structured data)处理的Spark模块。

与基本的Spark RDD API不同,Spark SQL的抽象数据类型为Spark提供了关于数据结构和正在执行的计算的更多信息。

在内部,Spark SQL使用这些额外的信息去做一些额外的优化,有多种方式与Spark SQL进行交互,比如: SQL和DatasetAPI。

当计算结果的时候,使用的是相同的执行引擎,不依赖你正在使用哪种API或者语言。这种统一也就意味着开发者可以很容易在不同的API之间进行切换,这些API提供了最自然的方式来表达给定的转换。

我们已经学习了Hive,它是将Hive SQL转换成 MapReduce然后提交到集群上执行,大大简化了编写 MapReduce的程序的复杂性,由于MapReduce这种计算模型执行效率比较慢。所以Spark SQL的应运而生,它是将Spark SQL转换成RDD,然后提交到集群执行,执行效率非常快!

Spark SQL它提供了2个编程抽象,类似Spark Core中的RDD

  • DataFrame

  • DataSet

1.2 Spark SQL的特点

1.2.1 易整合

无缝的整合了 SQL 查询和 Spark 编程

image-20201116104753362

1.2.2 统一的数据访问方式

使用相同的方式连接不同的数据源

image-20201116104820542

1.2.3 兼容Hive

在已有的仓库上直接运行 SQL 或者 HiveQL

image-20201116105101785

1.2.4 标准的数据连接

通过 JDBC 或者 ODBC 来连接

image-20201116105129447

1.3 什么是DataFrame

在Spark中,DataFrame是一种以RDD为基础的分布式数据集,类似于传统数据库中的二维表格。DataFrame与RDD的主要区别在于,前者带有schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型。这使得Spark SQL得以洞察更多的结构信息,从而对藏于DataFrame背后的数据源以及作用于DataFrame之上的变换进行了针对性的优化,最终达到大幅提升运行时效率的目标。反观RDD,由于无从得知所存数据元素的具体内部结构,Spark Core只能在stage层面进行简单、通用的流水线优化。

同时,与Hive类似,DataFrame也支持嵌套数据类型(struct、array和map)。从 API 易用性的角度上看,DataFrame API提供的是一套高层的关系操作,比函数式的RDD API 要更加友好,门槛更低。

image-20201116105339388

上图直观地体现了DataFrame和RDD的区别。

左侧的RDD[Person]虽然以Person为类型参数,但Spark框架本身不了解Person类的内部结构。而右侧的DataFrame却提供了详细的结构信息,使得 Spark SQL 可以清楚地知道该数据集中包含哪些列,每列的名称和类型各是什么。

DataFrame是为数据提供了Schema的视图。可以把它当做数据库中的一张表来对待

DataFrame也是懒执行的,但性能上比RDD要高,主要原因:优化的执行计划,即查询计划通过Spark catalyst optimiser进行优化。比如下面一个例子:

image-20201116105520281

image-20201116105525753

为了说明查询优化,我们来看上图展示的人口数据分析的示例。图中构造了两个DataFrame,将它们join之后又做了一次filter操作。

如果原封不动地执行这个执行计划,最终的执行效率是不高的。因为join是一个代价较大的操作,也可能会产生一个较大的数据集。如果我们能将filter下推到 join下方,先对DataFrame进行过滤,再join过滤后的较小的结果集,便可以有效缩短执行时间。而Spark SQL的查询优化器正是这样做的。简而言之,逻辑查询计划优化就是一个利用基于关系代数的等价变换,将高成本的操作替换为低成本操作的过程。

image-20201116105707931

1.4 什么是DataSet

DataSet是分布式数据集合。DataSet是Spark 1.6中添加的一个新抽象,是DataFrame的一个扩展。它提供了RDD的优势(强类型,使用强大的lambda函数的能力)以及Spark SQL优化执行引擎的优点。DataSet也可以使用功能性的转换(操作map,flatMap,filter等等)。

  • 是DataFrame API的一个扩展,是SparkSQL最新的数据抽象

  • 用户友好的API风格,既具有类型安全检查也具有DataFrame的查询优化特性;

  • 用样例类来定义DataSet中数据的结构信息,样例类中每个属性的名称直接映射到DataSet中的字段名称;

  • DataSet是强类型的。比如可以有DataSet[Car],DataSet[Person]。

  • DataFrame是DataSet的特列,DataFrame=DataSet[Row] ,所以可以通过as方法将DataFrame转换为DataSet。Row是一个类型,跟Car、Person这些的类型一样,所有的表结构信息都用Row来表示。

image-20201116110033776

第2章 Spark SQL编程

2.1 SparkSession新的起始点

在老的版本中,SparkSQL提供两种SQL查询起始点:一个叫SQLContext,用于Spark自己提供的SQL查询;一个叫HiveContext,用于连接Hive的查询。

SparkSession是Spark最新的SQL查询起始点,实质上是SQLContext和HiveContext的组合,所以在SQLContex和HiveContext上可用的API在SparkSession上同样是可以使用的。SparkSession内部封装了sparkContext,所以计算实际上是由sparkContext完成的。当我们使用 spark-shell 的时候, spark 会自动的创建一个叫做spark的SparkSession, 就像我们以前可以自动获取到一个sc来表示SparkContext

image-20201116110927620

2.2 DataFrame

Spark SQL的DataFrame API 允许我们使用 DataFrame 而不用必须去注册临时表或者生成SQL表达式。DataFrame API 既有transformation操作也有action操作,DataFrame的转换从本质上来说更具有关系, 而 DataSet API 提供了更加函数式的 API

2.2.1 创建DataFrame

在Spark SQL中SparkSession是创建DataFrame和执行SQL的入口,创建DataFrame有三种方式:通过Spark的数据源进行创建;从一个存在的RDD进行转换;还可以从Hive Table进行查询返回。

1)从Spark数据源进行创建

查看Spark支持创建文件的数据源格式

scala> spark.read.
csv      jdbc   load     options   parquet   table   textFile
format   json   option   orc       schema    text

读取json文件创建DataFrame

scala> spark.read.json("/root/users.json")
res0: org.apache.spark.sql.DataFrame = [Age: bigint, name: string]

注意:如果从内存中获取数据,spark可以知道数据类型具体是什么,如果是数字,默认作为Int处理;但是从文件中读取的数字,不能确定是什么类型,所以用bigint接收,可以和Long类型转换,但是和Int不能进行转换

2)从RDD进行转换
查看下文

3)Hive Table进行查询返回
查看下文

2.2.2 SQL风格语法

SQL语法风格是指我们查询数据的时候使用SQL语句来查询,这种风格的查询必须要有临时视图或者全局视图来辅助

object SQLDemo {
   
  def main(args: Array[String]): Unit = {
   
    val conf: SparkConf = new SparkConf().setAppName("sql").setMaster("local[3]")
    val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
    val df: DataFrame = spark.read.json("in/users.json")
    df.createTempView("users")
    spark.sql("select * from users").show()
  }
}

注意:普通临时表是Session范围内的,如果想应用范围内有效,可以使用全局临时表。使用全局临时表时需要全路径访问,如:global_temp.users

df.createGlobalTempView("users")
spark.sql("select * from global_temp.users").show()

2.2.3 DSL风格语法

DataFrame提供一个特定领域语言(domain-specific language, DSL)去管理结构化的数据,可以在 Scala, Java, Python 和 R 中使用 DSL,使用 DSL 语法风格不必去创建临时视图了

object SQLDemo {
   
  def main(args: Array[String]): Unit = {
   
    val conf: SparkConf = new SparkConf().setAppName("sql").setMaster("local[3]")
    val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
    val df: DataFrame = spark.read.json("in/users.json")
    df.printSchema()
    //查询name
    df.select("name").show()
    //查看所有列
    df.select("*").show()
    //查看name数据以及age+1数据
    // 注意:涉及到运算的时候, 每列都必须使用$ 但是idea报错 spark-shell可以通过
    df.select(df("name"),df("age")+1).show()
    //查看”age”大于”19”的数据
    df.filter(df("age")>19).show()
    //按照”age”分组,查看数据条数
    df.groupBy("age").count().show()
  }
}

2.2.4 RDD转换为DataFrame

注意:如果需要RDD与DF或者DS之间操作,那么都需要引入 import spark.implicits._ (spark不是包名,而是sparkSession对象的名称,所以必须先创建SparkSession对象再导入. implicits是一个内部object)

  • 前置条件
    导入隐式转换并创建一个RDD
    在某个目录下准备people.txt
zangsan,12
lisi,22
wangwu,25
val conf: SparkConf = new SparkConf().setAppName("sql").setMaster("local[3]")
val sc = new SparkContext(conf)
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
import spark.implicits._
val rdd: RDD[String] = sc.textFile("in/people.txt")
  1. 通过手动确定转换
rdd.map(_.split(","))
  .map(x=>(x(0),x(1).toInt))
  .toDF("name","age")
  .show()
  1. 通过样例类反射转换(常用)

创建一个样例类

case class People(name:String,age:Int)

根据样例类将RDD转换为DataFrame

rdd.map(_.split(","))
  .map(x=>People(x(0),x(1).toInt))
  .toDF().show()
  1. 通过编程的方式(了解,一般编程直接操作RDD较少,操作hive或数据文件等较多)
package SQL

import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types.{
   IntegerType, StringType, StructField, StructType}
import org.apache.spark.{
   SparkConf, SparkContext}
import org.apache.spark.sql.{
   DataFrame, Row, SparkSession}

object SQLDemo {
   
  case class People(name:String,age:Int)
  def main(args: Array[String]): Unit = {
   
    val conf: SparkConf = new SparkConf().setAppName("sql").setMaster("local[3]")
    val sc = new SparkContext(conf)
    val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
    import spark.implicits._
    val rdd: RDD[(String, Int)] = sc.makeRDD(Array(("lisi",20),("wangwu",32),("zs",36)))
    // 映射出来一个 RDD[Row], 因为 DataFrame其实就是 DataSet[Row]
    val rowRdd: RDD[Row] = rdd.map(x=>Row(x._1,x._2))
    //创建StructType类型
    val schema = StructType(Array(
      StructField("name", StringType),
      StructField("age", IntegerType)
    ))
    val df: DataFrame = spark.createDataFrame(rowRdd,schema)
    df.show()
  }
}

2.2.5 DataFrame转换为RDD

直接调用rdd即可

df.rdd.collect().foreach(println)

注意:得到的RDD存储类型为Row

2.3DataSet

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

2.3.1 创建DataSet

1)使用样例类序列创建DataSet


scala> case class Person(name:String,age:Long)
defined class Person

scala> val ds=Seq(Person("wangwu",21),Person("lisi",20)).toD
toDF   toDS

scala> val ds=Seq(Person("wangwu",21),Person("lisi",20)).toDS
ds: org.apache.spark.sql.Dataset[Person] = [name: string, age: bigint]

scala> ds.show()
+------+---+
|  name|age|
+------+---+
|wangwu| 21|
|  lisi| 20|
+------+---+

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


scala> val ds=Seq(1,2,3,4,5).toDS
ds: org.apache.spark.sql.Dataset[Int] = [value: int]

scala> ds.show()
+-----+
|value|
+-----+
|    1|
|    2|
|    3|
|    4|
|    5|
+-----+

注意:在实际使用的时候,很少用到把序列转换成DataSet,更多是通过RDD来得到DataSet

2.3.2 RDD转换为DataSet

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


scala> val rdd=sc.textFile(&#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值