目录
概述
为什么学习Spark SQL:
Spark SQL的版本迭代
SparkSession
sparkSession概念解释:
特点
创建SparkSession
在spark-shell中创建
在IDEA中创建SparkSession
RDD,DataFrame 和 DataSet
RDD的局限性
什么是DataFrame
特点
DataFrame解释
DataFrame编程
DataSet
为什么产生DataSet
解释
为什么需要 DataFrame 和 DataSet
Spark SQL 程序编写步骤
创建DataFrame
DataFrame常用操作
DSL风格语法
SQL风格语法
DataFrame 支持的操作
以编程方式执行Spark SQL
编写 Spark SQL 查询程序
提交Spark任务
数据源
通用的load和save功能
Save Model
JDBC
从 MySQL 中加载数据(Spark Shell 方式)
将数据写入 MySQL 中(Spark Submit 方式)
JSON
Parquet Files
Spark On Yarn
Spark 整合 Hive
SparkSQL自定义聚合函数
SparkSQL 定义普通函数
定义 SparkSQL 的自定义聚集函数
SparkSQL 常用窗口分析函数
(版本:Spark 2.3.2)
Spark SQL 是 Spark 用来处理结构化数据(结构化数据可以来自外部结构化数据源也可以通过 RDD 获取)的一个模块,它提供了一个编程抽象叫做 DataFrame 并且作为分布式 SQL 查询引擎的作用。
外部的结构化数据源包括 JSON、Parquet(默认)、RMDBS、Hive 等。当前 Spark SQL 使用 Catalyst优化器来对 SQL 进行优化,从而得到更加高效的执行方案。并且可以将结果存储到外部系统。
我们已经学习了 Hive,它是将 Hive SQL 转换成 MapReduce 然后提交到集群上执行,大大简化了编写 MapReduce 的程序的复杂性,由于 MapReduce 这种计算模型执行效率比较慢。所以 Spark SQL 就应运而生,它的工作机制是将 Spark SQL 的 SQL 查询转换成 Spark Core 的应用程序,然后提交到集群执行,执行效率非常快!
SparkSession 是 Spark-2.0 引如的新概念。SparkSession 为用户提供了统一的切入点,来让用户学习 Spark 的各项功能。在 Spark 的早期版本中,SparkContext 是 Spark 的主要切入点,由于 RDD 是主要的 API,我们通过 sparkContext 来创建和操作 RDD。对于每个其他的 API,我们需要使用不同的 context。
例如:
对于 Spark Streaming,我们需要使用 StreamingContext
对于 Spark SQL,使用 SQLContext
对于 Hive,使用 HiveContext
但是随着 DataSet 和 DataFrame 的 API 逐渐成为标准的 API,就需要为他们建立接入点。所以在 Spark2.0 中,引入SparkSession 作为 DataSet 和 DataFrame API 的切入点,SparkSession封装了 SparkConf、SparkContext 和 SQLContext。为了向后兼容,SQLContext 和 HiveCont也被保存下来。SparkSession 实质上是 SQLContext 和 HiveContext 的组合,所以在 SQLContext 和 HiveContext上可用的 API 在 SparkSession 上同样是可以使用的。SparkSession 内部封装了 SparkContext,所以计算实际上是由 SparkContext 完成的。
[hadoop@hadoop02 ~]$ ~/apps/spark-2.3.1-bin-hadoop2.7/bin/spark-shell \
> --master spark://hadoop02:7077 \
> --executor-memory 512m \
> --total-executor-cores 1
SparkSession 会被自动初始化一个对象叫做 spark,为了向后兼容,Spark-Shell 还提供了一个sparkContext 的初始化对象,方便用户操作:
RDD 仅表示数据集,RDD 没有元数据,也就是说没有字段语义定义。它需要用户自己优化程序,对程序员要求较高,从不同数据源读取数据相对困难,读取到不同格式的数据都必须用户自己定义转换方式合并多个数据源中的数据也较困难。
SparkCore的RDD编程
1)首先要找到程序入口(SparkContext)
2)通过程序入口构建一个 RDD(核心的抽象 RDD)
3)对写 RDD 进行 Transformation 或者 Action 的操作
4)对最后的结果进行处理(输出或者存入数据库等)
由于 RDD 的局限性,Spark 产生了 DataFrame,其中 Schema 是就是元数据,是语义描述信息。在 Spark1.3 之前,DataFrame 被称为SchemaRDD。以行为单位构成的分布式数据集合,按照列赋予不同的名称。对 select、fileter、aggregation 和 sort 等操作符的抽象。
DataFrame = RDD+Schema = SchemaRDD
与 RDD 类似,DataFrame 也是一个分布式数据容器。然而 DataFrame 更像传统数据库的二维表格,除了数据以外,还记录数据的结构信息,即 Schema。同时,与 Hive 类似,DataFrame也支持嵌套数据类型(struct、array 和 map)。从 API 易用性的角度上 看,DataFrame API提供的是一套高层的关系操作,比函数式的 RDD API
A DataFrame is a Dataset organized into named columns. It is conceptually equivalent to a table in a relational database or a data frame in R/Python, but with richer optimizations under the hood. DataFrames can be constructed from a wide array of sources such as: structured data files, tables in Hive, external databases, or existing RDDs. The DataFrame API is available in Scala, Java, Python, and R. In Scala and Java, a DataFrame is represented by a Dataset of Row
s. In the Scala API, DataFrame
is simply a type alias of Dataset[Row]
. While, in Java API, users need to use Dataset
to represent a DataFrame
.
翻译:
DataFrame 是按列名的方式去组织的一个分布式的数据集(RDD),就像关系型数据库里面的一张表,(或者说好比是 R/Python 语言里面的 DataFrame),不过 SparkSQL 这儿的方法比 R/Python 语言里面的 DataFrame 提供的操作方法更丰富,DataFrame 的数据源有如下:结构化的文件,Hive 里面的表,外部的数据库(MySQL 等),已经存在的 RDD。DataFrame 提供了 Scala,Java,Python,R 的编程 API,在 Scala 或者 Java 编程中,一个 DataFrame 表示以行组织的 Rows 的数据集合,在 Scala 的 API 中,DataFrame 就可以看做是 Dataset[Row]的另一种称呼,但是,在 Java 的 API 中,开发者必须使用 Dataset
Spark SQL 编程:
由于 DataFrame 的数据类型统一是 Row,所以 DataFrame 也是有缺点的。Row 运行时类型检查,比如 salary 是字符串类型,下面语句也只有运行时才进行类型检查。所以,Spark SQL 引入了 Dataset,扩展了 DataFrame API,提供了编译时类型检查,面向对象风格的 API。但是Dataset 可以和 DataFrame、RDD 相互转换。DataFrame=Dataset[Row],可见 DataFrame 是一种特殊的 Dataset。
dataframe.filter("salary>1000").show()
A Dataset is a distributed collection of data. Dataset is a new interface added in Spark 1.6 that provides the benefits of RDDs (strong typing, ability to use powerful lambda functions) with the benefits of Spark SQL’s optimized execution engine. A Dataset can be constructed from JVM objects and then manipulated using functional transformations (map, flatMap, filter, etc.). The Dataset API is available in Scala and Java. Python does not have the support for the Dataset API. But due to Python’s dynamic nature, many of the benefits of the Dataset API are already available (i.e. you can access the field of a row by name naturally row.columnName). The case for R is similar.
翻译:
一个 Dataset 是一个分布式的数据集合 Dataset 是在 Spark 1.6 中被添加的新接口,它提供了RDD 的优点(强类型化,能够使用强大的 lambda 函数)与 Spark SQL 执行引擎的优点。一个 Dataset 可以从 JVM 对象来构造并且使用转换功能(map, flatMap, filter,等等)。Dataset API 在 Scala 和 Java 是可用的。Python 不支持 Dataset API。但是由于 Python 的动态特性,许多Dataset API 的优点已经可用了 (也就是说,你可能通过 name 天生的 row.columnName 属性访问一行中的字段)。这种情况和 R 相似。
Spark SQL提供了两种方式读取操作数据:1. SQL 查询 2. DataFrame 和 Dataset API。但是,SQL 语句虽然简单,但是 SQL 的表达能力却是有限的,DataFrame 和 Dataset 可以采用更加通用的语言(Scala 或 Python)来表达用户的查询请求。此外,Dataset 可以更快捕捉错误,因为 SQL 是运行时捕获异常,而 Dataset 是编译时检查错误。
数据文件 :
//打印 DataFrame 的 Schema 信息
studentDF.printSchema
DSL风格语法示例:
//查看 DataFrame 部分列中的内容
studentDF.select("name", "age").show
studentDF.select(col("name"), col("age")).show
studentDF.select(studentDF.col("name"), studentDF.col("age")).show
//查询所有的 name 和 age,并将 age+1
studentDF.select(col("id"), col("name"), col("age") + 1).show
studentDF.select(studentDF ("id"), studentDF ("name"), studentDF ("age") + 1).show
// 按年龄进行分组并统计相同年龄的人数
studentDF.groupBy("age").count().show()
注:如果想使用 SQL 风格的语法,需要将 DataFrame 注册成表
老版本写法:
新版本写法:
SQL风格语法示例:
// 查询年龄最大的前五名
sqlContext.sql("select * from t_student order by age desc limit 5").show
// 显示表的 Schema 信息
sqlContext.sql("desc t_student ").show
// 统计学生数超过 6 个的部门和该部门的学生人数。并且按照学生的个数降序排序
sqlContext.sql("select department, count(*) as total from t_student group by department having total > 6 order by total desc").show
package com.mazh.spark.sql
import org.apache.spark.sql.{SQLContext, SparkSession}
import org.apache.spark.{SparkConf, SparkContext}
//case class 一定要事先放到外面定义好
case class Student(id: Int, name: String, sex: String, age: Int, department: String)
object StudentSparkSQL {
def main(args: Array[String]) {
//创建 SparkConf()并设置 App 名称
val conf = new SparkConf().setAppName("FirstSparkSQLAPP--Student")
//SQLContext 要依赖 SparkContext
val sc = new SparkContext(conf)
//创建 SQLContext
val sqlContext = new SQLContext(sc)
//从指定的地址创建 RDD
val lineRDD = sc.textFile(args(0)).map(_.split(","))
//创建 case class
//将 RDD 和 case class 关联
val studentRDD = lineRDD.map(x => Student(x(0).toInt, x(1), x(2), x(3).toInt,x(4)))
//导入隐式转换,如果不导入无法将 RDD 转换成 DataFrame
//将 RDD 转换成 DataFrame
import sqlContext.implicits._
val studentDF = studentRDD.toDF
//注册表
studentDF.registerTempTable("t_student")
//传入 SQL
val df = sqlContext.sql("select department, count(*) as total from t_student group by department having total > 6 order by total desc")
//将结果以 JSON 的方式存储到指定位置
df.write.json(args(1))
//停止 Spark Context
sc.stop()
}
}
$SPARK_HOME/bin/spark-submit \
--class com.mazh.spark.sql.StudentSparkSQL \
--master spark://hadoop02:7077,hadoop04:7077 \
/home/hadoop/Spark_SQL-1.0-SNAPSHOT.jar \
hdfs://myha01/student/student.txt \
hdfs://myha01/student/output_sparksql
编写普通的 load 和 save 功能
spark.read.load("hdfs://myha01/spark/sql/input/users.parquet").select("name","favorite_color").write.save("hdfs://myha01/spark/sql/output")
指定 load 和 save 的特定文件格式
spark.read.format("json").load("hdfs://myha01/spark/sql/input/people.json").select("name", "age").write.format("csv").save("hdfs://myha01/spark/sql/csv")
Spark SQL 可以通过 JDBC 从关系型数据库中读取数据的方式创建 DataFrame,通过对 DataFrame 一系列的计算后,还可以将数据再写回关系型数据库中。
启动 Spark Shell,必须指定 mysql 连接驱动 jar 包
启动本机的单进程 Shell:
$SPARK_HOME/bin/spark-shell \
--jars $SPARK_HOME/mysql-connector-java-5.1.40-bin.jar \
--driver-class-path $SPARK_HOME/mysql-connector-java-5.1.40-bin.jar
启动连接 Spark 集群的 Shell:
$SPARK_HOME/bin/spark-shell \
--master spark://hadoop02:7077,hadoop04:7077 \
--jars $SPARK_HOME/mysql-connector-java-5.1.40-bin.jar \
--driver-class-path $SPARK_HOME/mysql-connector-java-5.1.40-bin.jar
从 mysql 中加载数据
val jdbcDF = sqlContext.read.format("jdbc").options(Map("url" -> "jdbc:mysql://hadoop02:3306/spider", "driver" -> "com.mysql.jdbc.Driver", "dbtable" -> "lagou", "user" -> "root", "password" -> "root")).load()
package com.mazh.spark.sql
import java.util.Properties
import org.apache.spark.sql.types.{IntegerType, StringType, StructField,StructType}
import org.apache.spark.sql.{Row, SQLContext}
import org.apache.spark.{SparkConf, SparkContext}
object SparkSQL_JDBC {
def main(args: Array[String]) {
val conf = new SparkConf().setAppName("SparkSQL_JDBC")
val sc = new SparkContext(conf)
val sqlContext = new SQLContext(sc)
//通过并行化创建 RDD
// val studentRDD = sc.parallelize(Array("1 huangbo 33", "2 xuzheng 44", "3 wangbaoqiang 55")).map(_.split(" "))
//通过读取文件创建 RDD
val studentRDD = sc.textFile(args(0)).map(_.split(","))
//通过 StructType 直接指定每个字段的 schema
val schema = StructType(
List(
StructField("id", IntegerType, true),
StructField("name", StringType, true),
StructField("sex", StringType, true),
StructField("age", IntegerType, true),
StructField("department", StringType, true)
)
)
//将 RDD 映射到 rowRDD
val rowRDD = studentRDD.map(p => Row(p(0).toInt, p(1).trim, p(2).trim,p(3).toInt,p(4).trim))
//将 schema 信息应用到 rowRDD 上
val studentDataFrame = sqlContext.createDataFrame(rowRDD, schema)
//创建 Properties 存储数据库相关属性
val prop = new Properties()
prop.put("user", "root")
prop.put("password", "root")
//将数据追加到数据库
studentDataFrame.write.mode("append").jdbc("jdbc:mysql://hadoop02:3306/spider","student", prop)
//停止 SparkContext
sc.stop()
}
}
准备数据:student.txt 存储在 HDFS 上的/student 目录中
给项目打成 jar 包,上传到客户端
提交任务给 Spark 集群:
$SPARK_HOME/bin/spark-submit \
--class com.mazh.spark.sql.SparkSQL_JDBC \
--master spark://hadoop02:7077,hadoop04:7077 \
--jars $SPARK_HOME/mysql-connector-java-5.1.40-bin.jar \
--driver-class-path $SPARK_HOME/mysql-connector-java-5.1.40-bin.jar \
/home/hadoop/Spark_WordCount-1.0-SNAPSHOT.jar \
hdfs://myha01/student/student.txt
结果展示:
代码:
object TestSparkSQL_ReadJSON {
def main(args: Array[String]): Unit = {
// 构建 SparkSQL 程序的编程入口对象 SparkSession
val sparkSession:SparkSession = SparkSession.builder()
.appName("MyFirstSparkSQL")
.config("someKey", "someValue")
.master("local")
.getOrCreate()
// 方式 1
val df1 = sparkSession.read.json("D:\\bigdata\\json\\people.json")
// 方式 2
val df2 = sparkSession.read.format("json").load("D:\\bigdata\\json\\people.json")
}
}
代码:
object TestSparkSQL_ReadParquet {
def main(args: Array[String]): Unit = {
// 构建 SparkSQL 程序的编程入口对象 SparkSession
val sparkSession:SparkSession = SparkSession.builder()
.appName("MyFirstSparkSQL")
.config("someKey", "someValue")
.master("local")
.getOrCreate()
// 方式 1
val df1 = sparkSession.read.parquet("D:\\bigdata\\parquet\\people.parquet")
// 方式 2
val df2 = sparkSession.read.format("parquet").load("D:\\bigdata\\json\\people.json")
}
}
参照博客:https://blog.csdn.net/Jerry_991/article/details/85042305
参照博客:https://blog.csdn.net/Jerry_991/article/details/84000097
要点:spark.udf.register(“function_name”, function)
/*
* 第一步:获取程序入口
*/
val sparkConf = new SparkConf()
sparkConf.setAppName("SparkSQL_UAF_Length").setMaster("local")
val sparkContext = new SparkContext(sparkConf)
val sqlContext = new SQLContext(sparkContext)
/*
* 第二步:获取到一个DataFrame,然后注册为一张表
*
* JDBC:三个参数
* url:String
* table:String
* properties:Properties
*/
val url = "jdbc:mysql://hadoop02:3306/bigdata"
val table = "student"
val properties = new Properties()
properties.put("user","root")
properties.put("password","root")
val studentDF:DataFrame = sqlContext.read.jdbc(url,table,properties)
/*
* 第三步:把这个dataFrame注册为一张临时表
*/
studentDF.createTempView("student")
/*
* 第四步:定义一个函数
*/
sqlContext.udf.register("strLength",(x:String) => x.length)
/*
* 第五步:使用这个函数做一个操作,求出某个字段的长度
*/
sqlContext.sql("select strLength(name) as name_len from student").show()
/*
* 第六步:程序完结,关闭资源
*/
sparkContext.stop()
要点:Class MyUDAF extends UserDefinedAggregationFunction,spark.udf.register("function_name", function)
object SparkSQL_UDAF_AvgAge extends UserDefinedAggregateFunction{
/**
* 定义输入的数据的类型
*/
override def inputSchema: StructType = StructType(
StructField("age", DoubleType, true) :: Nil
)
/**
* 定义辅助字段:
*
* 1、辅助字段 1:用来记录所有年龄之和 total
* 2、辅助字段 2:用来总记录所有学生的个数 count
*/
override def bufferSchema: StructType = StructType(
StructField("total", DoubleType, true)::
StructField("count", IntegerType, true)::
Nil
)
/**
* 计算学生的平均年龄 计算公式: 学生年龄的总和 / 学生总数
*
* 所以要初始化要两个辅助字段:
* total : 0.0
* count : 0
*/
override def initialize(buffer: MutableAggregationBuffer): Unit = {
buffer.update(0, 0.0)
buffer.update(1, 0)
}
/**
* 每次给一条记录, 然后进行累加。进行累加变量 buffer 的状态更新
* 这是一个局部操作。
*/
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
val lastTotal = buffer.getDouble(0)
val lastCount = buffer.getInt(1)
val currentSalary = input.getDouble(0)
buffer.update(0,lastTotal + currentSalary)
buffer.update(1,lastCount+1)
}
/**
* 当局部操作完成,最后需要一个全局合并的操作
* 就相当于是 reducer 阶段的最终合并
*/
override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
val total1 = buffer1.getDouble(0)
val count1 = buffer1.getInt(1)
val total2 = buffer2.getDouble(0)
val count2 = buffer2.getInt(1)
buffer1.update(0, total1 + total2)
buffer1.update(1, count1 + count2)
}
/**
* 计算平均年龄
*/
override def evaluate(buffer: Row): Any = {
val total = buffer.getDouble(0)
val count = buffer.getInt(1)
total / count
}
/**
* 返回结果数据类型
*/
override def dataType: DataType = DoubleType
/**
* 输入和输出的字段类型是否匹配。也即是否一致
*/
override def deterministic: Boolean = true
}
使用测试:
(待整理)