大数据技术之Spark SQL

一:Spark SQL的概述

  1. 定义:Spark SQL是Spark用来处理结构化数据的一个模块,它提供了2个编程对象:DataFrame和DataSet,并且作为分布式SQL查询引擎的作用。
  2. 特点:易整合、统一的数据访问方式、兼容Hive、标准的数据连接
  3. DtaFrame的定义:与RDD类似,DataFrame也是一个分布式的数据容器。然而Dataframe更像是一个数据库的二维表格,除了有数据以外,还记录着数据的结构信息,即schema
  4. DataSet的定义:DataSet是DtaFrame API的一个扩展,是Spark最新的数据抽象。
    1) 是 Dataframe API 的一个扩展,是 Spark 最新的数据抽象。
    2) 用户友好的 API 风格,既具有类型安全检查也具有 Dataframe 的查询优化特性。
    3) Dataset 支持编解码器, 当需要访问非堆上的数据时可以避免反序列化整个对象,提高了
    效率。
    4) 样例类被用来在 Dataset 中定义数据的结构信息,样例类中每个属性的名称直接映射到
    4 / 26 北京东燕郊开发区燕灵路方舟广场南侧 169 号 电话: 010-83868569
    DataSet 中的字段名称。
    5) Dataframe 是 Dataset 的特列, DataFrame=Dataset[Row] ,所以可以通过 as 方法将 Dataframe
    转换为 Dataset。 Row 是一个类型,跟 Car、 Person 这些的类型一样,所有的表结构信息我都用
    Row 来表示。
    6) DataSet 是强类型的。比如可以有 Dataset[Car], Dataset[Person].
    7) DataFrame 只是知道字段,但是不知道字段的类型,所以在执行这些操作的时候是没办法
    在编译的时候检查是否类型失败的,比如你可以对一个 String 进行减法操作,在执行的时候才报
    错,而 DataSet 不仅仅知道字段,而且知道字段类型,所以有更严格的错误检查。 就跟 JSON 对
    象和类对象之间的类比

二:Spark SQL 编程

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

SparkSession 是Spark最新的 SQL 查询起始点,实质上是 SQLContext 和 HiveContext 的组合,所以在 SQLContext 和 HiveContext 上可用的 API 在 SparkSession 上同样是可以使用的。SparkSession 内部封装了 sparkContext,所以计算实际上是由 sparkContext 完成的。

  1. DataFrame: 在 Spark SQL 中 SparkSession 是创建 DataFrame 和执行 SQL 的入口,创建 DataFrame 有三种方式: 通过 Spark 的数据源进行创建; 从一个存在的 RDD 进行转换; 还可以从 Hive Table 进行查询返回。
    1)数据源创建
1. 查看Spark数据源进行创建的文件格式
spark.read
2. 读取json文件创建DataFrame
val df=spark.read.json("/opt/module/spark/examples/src/main/resources/people.json")

2)从RDD进行转换创建

3)从Hive Table进行查询返回创建

后续第三章着重介绍

  1. SQL的语法风格
1. 创建一个DataFrame
val df=spark.read.json("/opt/module/spark/examples/src/main/resources/people.json")
2.对 DataFrame 创建一个临时表
scala> df.createOrReplaceTempView("people")
注意: 临时表是 Session 范围内的, Session 退出后,表就失效了。如果想应用范围内有效,可以
使用全局表。注意使用全局表时需要全路径访问, 如: global_temp.people
3. 对于 DataFrame 创建一个全局表
scala> df.createGlobalTempView("people")
4. 通过 SQL 语句实现查询全表
scala> spark.sql("SELECT * FROM global_temp.people").show()
scala> spark.newSession().sql("SELECT * FROM global_temp.people").show()
  1. RDD转换为DataFrame
  1. 注意: 如果需要 RDD 与 DF 或者 DS 之间操作,那么都需要引入 import spark.implicits._ 【 spark
    不是包名,而是 sparkSession 对象的名称】
    前置条件: 导入隐式转换并创建一个 RDD 。
scala> import spark.implicits._
import spark.implicits._
scala> val peopleRDD = sc.textFile("examples/src/main/resources/people.txt")
peopleRDD: org.apache.spark.rdd.RDD[String] = examples/src/main/resources/people.txt MapPartitionsRDD[3] at
textFile at <console>:27
1)通过手动确定转换
scala> peopleRDD.map{x=>val para = x.split(",");(para(0),para(1).trim.toInt)}.toDF("name","age")
  1. DataFrame转换为RDD
  1. 直接调用rdd即可
1) 创建一个 DataFrame
scala> val df = spark.read.json("/opt/module/spark/examples/src/main/resources/people.json")
2)将 DataFrame 转换为 RDD
scala> val dfToRDD = df.rdd

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

  1. 创建DataSet
1. 创建一个样例类
case class Person(nam: String,age:Long)
2. 创建 DataSet
val caseClassDS = Seq(Person("Andy", 32)).toDS()
  1. RDD转换为DataSet

SparkSQL 能够自动将包含有 case 类的 RDD 转换成 DataFrame, case 类定义了 table 的结构,
case 类属性通过反射变成了表的列名。

1. 创建一个 RDD
val peopleRDD = sc.textFile("examples/src/main/resources/people.txt")
2. 创建一个样例类
case class Person(name: String, age: Long)
3. 将 RDD 转化为 DataSet
scala> peopleRDD.map(line => {val para = line.split(",");Person(para(0),para(1).trim.toInt)}).toDS()
  1. DataSet转换为RDD:调用rdd方法即可
1) 创建一个 DataSet
scala> val DS = Seq(Person("Andy", 32)).toDS()
2)将 DataSet 转换为 RDD
scala> DS.rdd
  1. DataFrame互相转换为Data’’Set
1)创建一个 DateFrame
scala> val df = spark.read.json("examples/src/main/resources/people.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
2)创建一个样例类
scala> case class Person(name: String, age: Long)
defined class Person
3)将 DateFrame 转化为 DataSet
scala> df.as[Person]
res14: org.apache.spark.sql.Dataset[Person] = [age: bigint, name: string]
2. DataSet 转换为 DataFrame
1)创建一个样例类
scala> case class Person(name: String, age: Long)
defined class Person
2)创建 DataSet
scala> val ds = Seq(Person("Andy", 32)).toDS()
ds: org.apache.spark.sql.Dataset[Person] = [name: string, age: bigint]
3)将 DataSet 转化为 DataFrame
scala> val df = ds.toDF

总结:DataSet转换DataFrame的方法很简单,只需要把case class封装成ROW即可,导入隐式转换后调用toDF。DataFrame转DataSet也比较简单,只需要导入隐式转换后调用as【row】方法,将其具体数据类型标注清除即可

  1. 三者共性
    1、 RDD、 DataFrame、 Dataset 全都是 spark 平台下的分布式弹性数据集,为处理超大型数据
    提供便利
    2、三者都有惰性机制,在进行创建、转换,如 map 方法时,不会立即执行,只有在遇到 Action
    如 foreach 时,三者才会开始遍历运算。
    3、三者都会根据 spark 的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存
    溢出。
    4、三者都有 partition 的概念
    5、三者有许多共同的函数,如 filter,排序等
    6、在对 DataFrame 和 Dataset 进行操作许多操作都需要这个包进行支持
    import spark.implicits._
    7、 DataFrame 和 Dataset 均可使用模式匹配获取各个字段的值和类型
  2. 三者区别
    1.RDD:
    1) RDD 一般和 spark mlib 同时使用
    2) RDD 不支持 sparksql 操作
    2.DataFrame:
    1) 与 RDD 和 Dataset 不同, DataFrame 每一行的类型固定为 Row, 每一列的值没法直接访问, 只有通过解析才能获取各个字段的值,
    2) DataFrame 与 Dataset 一般不与 spark mlib 同时使用
    3) DataFrame 与 Dataset 均支持 sparksql 的操作,比如 select, groupby 之类,还能注册临时表/视窗,进行 sql 语句操作
    4) DataFrame 与 Dataset 支持一些特别方便的保存方式,比如保存成 csv,可以带上表头,这样每一列的字段名一目了然
    3.DataSet:
    1) Dataset 和 DataFrame 拥有完全相同的成员函数,区别只是每一行的数据类型不同。
    2) DataFrame 也可以叫 Dataset[Row],每一行的类型是 Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的 getAS 方法或者共性中的第七条提到的模式匹配拿出特定字段。 而 Dataset 中,每一行是什么类型是不一定的,在自定义了 case class之后可以很自由的获得每一行的信息
    可以看出, Dataset 在需要访问列中的某个字段时是非常方便的,然而,如果要写一些适配性
    很强的函数时,如果使用 Dataset,行的类型又不确定,可能是各种 case class,无法实现适配, 这时候用 DataFrame 即 Dataset[Row]就能比较好的解决问题 。
  3. IDEA创建SparkSQL以及用户自定义UDF
package com.ityouxin.sparkSql

import org.apache.spark.sql.SparkSession

object HelloWorld {
  def main(args: Array[String]): Unit = {
    //创建 SparkConf()并设置 App 名称
    val spark = SparkSession
      .builder()
      .master("local[*]")
      .appName("Spark SQL basic example")
      .config("spark.some.config.option", "some-value")
      .getOrCreate()
    import  spark.implicits._
    val df = spark.read.json("datas/people.json")
    df.show()
    df.filter($"age" > 21).show()
    df.createOrReplaceTempView("persons")
    spark.sql("SELECT * FROM persons where age > 21").show()
    spark.stop()
  }
}

四:Spark SQL的数据源

  1. 通用加载/保存方法
  1. 手动指定选项
    Spark SQL 的 DataFrame 接口支持多种数据源的操作。一个 DataFrame 可以进行 RDDs 方式的操作,也可以被注册为临时表。把 DataFrame 注册为临时表之后,就可以对该 DataFrame 执行SQL 查询。
    Spark SQL 的默认数据源为 Parquet 格式。数据源为 Parquet 文件时, Spark SQL 可以方便的执行所有的操作。修改配置项 spark.sql.sources.default,可修改默认数据源格式。
    当数据源格式不是 parquet 格式文件时,需要手动指定数据源的格式。数据源格式需要指定全名(例如: org.apache.spark.sql.parquet),如果数据源格式为内置格式,则只需要指定简称定json, parquet, jdbc, orc, libsvm, csv, text 来指定数据的格式。可以通过 SparkSession 提供的 read.load 方法用于通用加载数据,使用 write 和 save 保存数据。
val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
val sqlDF = spark.sql("SELECT * FROM parquet.`hdfs://hadoop102:9000/namesAndAges.parquet`")
  1. 文件保存选项
    可以采用 SaveMode 执行存储操作, SaveMode 定义了对数据的处理模式。需要注意的是,这些保存模式不使用任何锁定,不是原子操作。此外,当使用 Overwrite 方式执行时,在输出新数据之前原数据就已经被删除。 SaveMode 详细介绍如下表: 略
  2. JSON

Spark SQL 能够自动推测 JSON 数据集的结构,并将它加载为一个 Dataset[Row]. 可以通过SparkSession.read.json()去加载一个 一个 JSON 文件。
注意: 这个 JSON 文件不是一个传统的 JSON 文件,每一行都得是一个 JSON 串。

val path = "examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)
peopleDF.printSchema()
  1. Parquet 文件

Parquet 是一种流行的列式存储格式,可以高效地存储具有嵌套字段的记录。 Parquet 格式经常在Hadoop 生态圈中被使用,它也支持 Spark SQL 的全部数据类型。 Spark SQL 提供了直接读取和存储 Parquet 格式文件的方法。

importing spark.implicits._
import spark.implicits._
val peopleDF = spark.read.json("examples/src/main/resources/people.json")
peopleDF.write.parquet("hdfs://hadoop102:9000/people.parquet")
val parquetFileDF = spark.read.parquet("hdfs:// hadoop102:9000/people.parquet")
parquetFileDF.createOrReplaceTempView("parquetFile")
val namesDF = spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")
namesDF.map(attributes => "Name: " + attributes(0)).show()
  1. JDBC

Spark SQL 可以通过 JDBC 从关系型数据库中读取数据的方式创建 DataFrame,通过对DataFrame 一系列的计算后,还可以将数据再写回关系型数据库中。
注意:需要将相关的数据库驱动放到 spark 的类路径下。

/load 默认的加载文件的格式由参数spark.sql.source.default决定
    val df = spark.read.load("datas/users.parquet")
    df.show()
   // spark.sql("select * from parquet.'datas/users.parquet'").show()
    //spark.sql("select * from json.'datas/people.json'").show()
  //jdbc
    spark.read.format("jdbc")
      .option("url","jdbc:mysql://localhost:3306/day10")
      .option("dbtable","user")
      .option("user","root")
      .option("password","123")
      .load().show()
    println("-------------------")
    val option = new Properties
    option.setProperty("user","root")
    option.setProperty("password","123")
    spark.read.jdbc("jdbc:mysql://localhost:3306/day10","user",option).show()
  1. Hive

Apach Hive 是 Hadoop 上的 SQL 引擎, Spark SQL 编译时可以包含 Hive 支持,也可以不包
含。包含 Hive 支持的 Spark SQL 可以支持 Hive 表访问、 UDF(用户自定义函数)以及 Hive 查询
语言(HiveQL/HQL)等。需要强调的一点是,如果要在 Spark SQL 中包含 Hive 的库,并不需要事
先安装 Hive。一般来说,最好还是在编译 Spark SQL 时引入 Hive 支持,这样就可以使用这些特
性了。如果你下载的是二进制版本的 Spark,它应该已经在编译时添加了 Hive 支持。