一 新的编程入口 SparkSession
SparkSession 是 Spark 最新的 SQL 查询起始点 ,实质上是 SQLcontext 和 SparkContext 的组合 ,所以在 SQLContext 和 HIveContext 上可用的 API 在 SparkSession 上同样是可以使用的 . SparkSession 内部封装了 sparkContext ,所以计算实际上是由 sparkContext 完成的 .
object SparkSqlTest {
def main(args: Array[String]): Unit = {
--创建SparkSQL 的上下文 ,sparkSession 后续简称为 spark
val spark: SparkSession = SparkSession.builder()
.appName(this.getClass.getSimpleName)
.master("local[*]")
.getOrCreate()
--SparkSession 就是对SparkContext的封装
--SparkSession 中持有 SparkContext的引用 ,对 SparkContext 增强了
--使用 SparkSession 创建DataFrame/DataSet(是对RDD的进一步封装 ,对RDD增强)
--DataFrame = RDD + Schema(元数据信息,字段名字,字段类型..)
val sc: SparkContext = spark.sparkContext
val lines: RDD[String] = sc.textFile(args(0))
val userRDD: RDD[User] = lines.map(line => {
val fields = line.split(",")
val f0 = fields(0)
val f1 = fields(1).toInt
val f2 = fields(2).toDouble
User(f0, f1, f2)
})
--导入SparkSession中的隐式转换 ,就可以调用toDF,将RDD转成DataFrame,然后可以使用RDD中的方法
--这里的spark实际上是 sparkSession 的简称
import spark.implicits._
--将userRDD转成 DataFrame
val userDF: DataFrame = userRDD.toDF
}
}
case class User(name: String, age: Int, fv: Double)
如果需要 Hive 支持 ,则需要以下创建语句
val spark = SparkSession.builder()
.appName(this.getClass.getSimpleName)
.config("spark.some.config.option","some-value")
.enableHiveSupport()
.getOrCreate()
二 创建 DataFrames
在Spark SQL 中 SparkSession 是创建 DataFrame 和执行 SQL 的入口, 创建DataFrame的方式有三种
核心要素 : 创建DataFrame ,需要创建 RDD+Schema(元数据信息)
1 从RDD创建DataFrame(从一个已经存在的RDD进行转换)
1) 创建 sparkSession ,后续简称 spark ;
2) 使用 spark 创建原始的 RDD ,对RDD里面的数据进行切割处理 ,将切割处理的数据封装到定义的一个样例类(bean对象)里面 ,返回一个新的 RDD ;
3) 创建 DataFrame 的两种方法 :
第一种 : spark 调用 createDataFrame ,将新的RDD 放进去
第二种 : 导入隐式转换(import spark.implicits._) , 然后新的RDD调用 toDF 方法将 RDD 转换成 DataFrame .
注意 : 如果切割处理的数据不封装到 bean对象里面 ,而是直接以 tuple(元组) 的方式返回生成新的RDD ,后续这个RDD转为 DataFrame 之后 ,其 ROW(行)字段的名字就不是元组里面的字段名字 ,框架从tuple元组结构中,对schema的推断,也是成功的,只是字段名是tuple中的数据访问索引。即 row 的描述信息没有被约束
object SparkSqlTest3 {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName(this.getClass.getSimpleName)
.master("local[*]")
.getOrCreate()
--创建RDD
val lines: RDD[String] = spark.sparkContext.parallelize(List("lii,13,90.00", "yuu,14,91.09", "koo,12,90.00"))
val userRDD: RDD[User2] = lines.map(line => {
val fields = line.split(",")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toDouble
User2(name, age, fv)
})
--创建DataFrame-----第一种方法
val userDF: DataFrame = spark.createDataFrame(userRDD)
userDF.printSchema()
/**
* root
* |-- name: string (nullable = true)
* |-- age: integer (nullable = false)
* |-- fv: double (nullable = false)
*/
userDF.show()
/**
* +----+---+-----+
* |name|age| fv|
* +----+---+-----+
* | lii| 13| 90.0|
* | yuu| 14|91.09|
* | koo| 12| 90.0|
* +----+---+-----+
*/
--创建DataFrame的----第二种方法--导入隐式转换
import spark.implicits._
val userDF2: DataFrame = userRDD.toDF
userDF2.show()
/**
* +----+---+-----+
* |name|age| fv|
* +----+---+-----+
* | lii| 13| 90.0|
* | yuu| 14|91.09|
* | koo| 12| 90.0|
* +----+---+-----+
*/
}
}
case class User2(name:String ,age:Int, fv:Double)
利用框架提供的隐式转换可以直接调用toDF创建,并指定字段名(其实就是约束 row 的信息)
object DataFrame03 {
def main(args: Array[String]): Unit = {
--创建sparksession
val session = SparkSession.builder()
.appName(this.getClass.getSimpleName)
.master("local[*]")
.getOrCreate()
--使用 sparksession 创建RDD
val lines: RDD[String] = session.sparkContext.parallelize(List("huu,12,98.00", "lii,13,99.09", "poo,14,98.09"))
val rowRDD = lines.map(line => {
val fields = line.split(",")
val f0 = fields(0)
val f1 = fields(1).toInt
val f2 = fields(2).toDouble
(f0, f1, f2)
})
--创建 DataFrame
val dataFrame: DataFrame = session.createDataFrame(rowRDD)
dataFrame.show() --打印创建的dataF
-- row 字段信息是元组的索引(字段名是tuple中的数据访问索引)
+---+---+-----+
| _1| _2| _3|
+---+---+-----+
|huu| 12| 98.0|
|lii| 13|99.09|
|poo| 14|98.09|
+---+---+-----+
--导入隐式转换
import session.implicits._
val dataFrame1: DataFrame = rowRDD.toDF("name", "age", "fv") --对 row 的信息进行约束
dataFrame1.show()
--结果如下:
+----+---+-----+
|name|age| fv|
+----+---+-----+
| huu| 12| 98.0|
| lii| 13|99.09|
| poo| 14|98.09|
+----+---+-----+
}
}
将切割处理的数据封装到Spark系统自定义的Row实例类里面 ,这样就可以给row指定字段属性了 ,创建的RDD跟跟row约束的字段名进行关联
--创建DataFrame = RDD+CaseClass ,然后调用RDD的toDF
--创建DataFrame = RDD+StructType
object DateFrame01 {
def main(args: Array[String]): Unit = {
--创建sparkSession ,简称 spark
val spark: SparkSession = SparkSession.builder()
.appName(this.getClass.getSimpleName)
.master("local[*]")
.getOrCreate()
--创建RDD
val lines: RDD[String] = spark.sparkContext.parallelize(List("HUU,13,98.0","YII,12,98.99","GRR,17,97.08"))
--处理数据 ,这个 Row 是spark系统自定义的实例类
val rowRDD: RDD[Row] = lines.map(line => {
val fields: Array[String] = line.split(",")
val f0 = fields(0)
val f1 = fields(1).toInt
val f2 = fields(2).toDouble
Row(f0, f1, f2)
})
--对Row的描述信息 ,就是所谓的Schema
val structType: StructType = StructType(List(
StructField("name", StringType), --该字段默认可以为空
StructField("age", IntegerType, false), --该字段不可以为空
StructField("fv", DoubleType, false)
))
--对RDD 和Schema 进行关联
val df: DataFrame = spark.createDataFrame(rowRDD, structType)
--创建视图
df.createTempView("v_user")
--查询数据
spark.sql(
"""
|select name,fv from v_user where age >= 13
|""".stripMargin).show()
----结果如下
+----+-----+
|name| fv|
+----+-----+
| HUU| 98.0|
| GRR|97.08|
+----+-----+
}
}
2 从JSON/Parquet/CSV/JDBC等结构化数据源直接创建DataFrame
2.1 json数据准备
{"name": "laozhao", "age": 18, "fv": 9999.99}
{"name": "laoduan", "age": 28, "fv": 999.99}
{"name": "nianhang", "age": 20, "fv": 999.99}
{"name": "heihei", "age": 20, "fv": 999.99
{"name": "nana", "age": 20, "fv": 999.99, "gender": "male"}
{"name": "test", "height": 180.2}
{"name": "laozhao", "age": 18, "fv": 9999.99}
{"name": "laoduan", "age": 28, "fv": 999.99}
{"name": "nianhang", "age": 20, "fv": 999.99}
{"name": "heihei", "age": 20, "fv": 999.99
2.2 从JSON结构化数据源直接创建DataFrame 的代码实现
object DataFrame04 {
def main(args: Array[String]): Unit = {
--创建sparksession ,简称spark
val spark: SparkSession = SparkSession.builder()
.appName(this.getClass.getSimpleName)
.master("local[*]")
.getOrCreate()
--DataFrame = RDD + Schema
-- RDD :以后从哪里读取数据
--Schema : 从 json 中获取信息
val dataFrame: DataFrame = spark.read.json(args(0))
val dataFrame1 = spark.read.format("json").load(args(0))
dataFrame1.printSchema()
/**
* root
* |-- _corrupt_record: string (nullable = true)
* |-- age: long (nullable = true)
* |-- fv: double (nullable = true)
* |-- gender: string (nullable = true)
* |-- height: double (nullable = true)
* |-- name: string (nullable = true)
*/
dataFrame1.show()
/**
* +--------------------+----+-------+------+------+--------+
* | _corrupt_record| age| fv|gender|height| name|
* +--------------------+----+-------+------+------+--------+
* | null| 18|9999.99| null| null| laozhao|
* | null| 28| 999.99| null| null| laoduan|
* | null| 20| 999.99| null| null|nianhang|
* |{"name": "heihei"...|null| null| null| null| null|
* | null| 20| 999.99| male| null| nana|
* | null|null| null| null| 180.2| test|
* | null| 18|9999.99| null| null| laozhao|
* | null| 28| 999.99| null| null| laoduan|
* | null| 20| 999.99| null| null|nianhang|
* |{"name": "heihei"...|null| null| null| null| null|
* +--------------------+----+-------+------+------+--------+
*/
}
}
2.2.1 将查询的数据信息存储到 mysql 里面 (注意 : 代码运行前需要将 mysql 下的数据库处于连接的状态 )
--创建视图
dataFrame1.createTempView("v_df")
--有问题的数据 _corrupt_record列不为null ,
--将正常的数据写到 mysql 里面
val df2: DataFrame = spark.sql(
"""
|select name,age,fv from v_df where _corrupt_record IS NULL
|""".stripMargin)
--定义一个配置对象
val properties = new Properties()
--设置用户名
properties.setProperty("user","root")
--设置密码
properties.setProperty("password","123456")
--将查询到的信息写到 mysql里面
df2.write.mode(SaveMode.Append).jdbc("jdbc:mysql://localhost:3306/db_data?characterEncoding=utf-8","t_user",properties)
2.2.2 mysql 里面写入的查询信息(存储的表格是系统自动创建的)
2.3 从CSV结构化数据源直接创建DataFrame
object CreateDataFrameFromCSV {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName(this.getClass.getSimpleName)
.master("local[*]")
.getOrCreate()
---------------创建DataFrame = RDD+Schema---------------------
---------第一种方式--------没有指定表头---------------------
--csv中没有schema ,读取csv文件中可以知道有多少列 ,每列都可以有默认的名称
val dataFrame = spark.read.csv(args(0))
dataFrame.toDF("name","age","fv")
--------第二种方式--------指定第一列为表头(第一列的数据为字段名字)--------------
val df: DataFrame = spark.read
.option("header", "true")
.option("inferSchema", "true")
.csv(args(0))
-----------第三种方式-----最佳方式-------------------
--对ROW的描述信息,就是所谓的Schema
val structType = StructType(List(
StructField("name", DataTypes.StringType),
StructField("age", IntegerType, false),
StructField("fv", DoubleType, false)
))
--指定表头,然后与描述信息相关联
val dataFrame1: DataFrame = spark.read.option("header", "true").schema(structType).csv(args(0))
dataFrame1.printSchema()
dataFrame1.show()
}
}
2.4 从Parquet结构化数据源直接创建DataFrame
object CreateDataFrameFromParquet01 {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName(this.getClass.getSimpleName)
.master("local[*]")
.getOrCreate()
--Parquet文件自带Schema ,读取parquet格式的文件,parquet文件默认使用的是snappy压缩,所以该文件是二进制形式存储的列式存储文件
val df: DataFrame = spark.read.parquet("d://sparkout")
df.printSchema()
/**
* root
* |-- name: string (nullable = true)
* |-- age: integer (nullable = true)
* |-- fv: double (nullable = true)
*/
df.show()
/**
* +----+---+-----+
* |name|age| fv|
* +----+---+-----+
* | HUU| 13| 98.0|
* | YII| 12|98.99|
* | GRR| 17|97.08|
* +----+---+-----+
*/
---因为parquet 是列式存储的文件 ,所以可以根据需求 ,有选择性的读取数据
df.select("age").show()
/**
* +---+
* |age|
* +---+
* | 13|
* | 12|
* | 17|
* +---+
*/
}
}
2.4 从JDBC结构化数据源直接创建DataFrame
object CreateDataFrameFromJDBC {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName(this.getClass.getSimpleName)
.master("local[*]")
.getOrCreate()
--定义一个配置对象 ,将mysql用户名和密码封装起来
val props = new Properties()
props.setProperty("user","root")
props.setProperty("password","123456")
--读取mysql里面指定的表里面的数据生成 dataFrame, 保证数据库是打开连接的状态
val dataFrame = spark.read.jdbc("jdbc:mysql://localhost:3306/db_data?characterEncoding=utf-8", "t_user", props)
dataFrame.printSchema()
/**
* root
* |-- name: string (nullable = true)
* |-- age: long (nullable = true)
* |-- fv: double (nullable = true)
*/
dataFrame.show()
/**
* +--------+----+-------+
* | name| age| fv|
* +--------+----+-------+
* | laozhao| 18|9999.99|
* | laoduan| 28| 999.99|
* |nianhang| 20| 999.99|
* | nana| 20| 999.99|
* | test|null| null|
* | laozhao| 18|9999.99|
* | laoduan| 28| 999.99|
* |nianhang| 20| 999.99|
* +--------+----+-------+
*/
}
}
3 从外部服务器读取数据创建DataFrame(从Hive Table进行查询)
三 DataFrame 数据运算操作
四 输出存储DataFrame
1 将数据以 parquet 的形式输出存储到 windows 的 d 盘
parquet 是列式存储型的文件 ,在读取文件时可以选择性的读取
object SaveToParquet01 {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName(this.getClass.getSimpleName)
.master("local[*]")
.getOrCreate()
val lines: RDD[String] = spark.sparkContext.parallelize(List("HUU,13,98.0","YII,12,98.99","GRR,17,97.08"))
--这里的 Row 是系统定义的一个类
val rowRDD: RDD[Row] = lines.map(line => {
val fields = line.split(",")
val f0 = fields(0)
val f1 = fields(1).toInt
val f2 = fields(2).toDouble
Row(f0, f1, f2)
})
--创建DataFrame的第一种方法------导入隐式转换-----------
import spark.implicits._
val df: DataFrame = rowRDD.toDF("name","age","fv")
df.createTempView("v_user")
--查询
spark.sql(
"""
|select name,fv from v_user where age >= 13
|""".stripMargin).show()
/**
* +----+-----+
* |name| fv|
* +----+-----+
* | HUU| 98.0|
* | GRR|97.08|
* +----+-----+
*/
-----创建DataFrame的第二种方法------StructType与RDD关联,调用createDataFrame方法-------
----对 ROW 的描述信息,就是所谓的Schema
val structType = StructType(List(
StructField("name", DataTypes.StringType),
StructField("age", IntegerType, false),
StructField("fv", DoubleType, false)
))
---将RDD和Schema进行关联
val dataFrame: DataFrame = spark.createDataFrame(rowRDD, structType)
dataFrame.repartition(2).write.mode(SaveMode.Append).parquet("d://sparkout")
dataFrame.write.parquet("d://sparkout1")
spark.stop()
}
}
1.1 dataFrame.repartition(2).write.mode(SaveMode.Append).parquet("d://sparkout") --
1.2 dataFrame.write.parquet("d://sparkout1")
2 将DataFrame 存储到 mysql 里面
--创建视图
dataFrame1.createTempView("v_df")
--有问题的数据 _corrupt_record列不为null ,
--将正常的数据写到 mysql 里面
val df2: DataFrame = spark.sql(
"""
|select name,age,fv from v_df where _corrupt_record IS NULL
|""".stripMargin)
--定义一个配置对象
val properties = new Properties()
--设置用户名
properties.setProperty("user","root")
--设置密码
properties.setProperty("password","123456")
--将查询到的信息写到 mysql里面 参数二 : 表名(存储查询数据的表格的名字)
df2.write.mode(SaveMode.Append).jdbc("jdbc:mysql://localhost:3306/db_data?characterEncoding=utf-8","t_user",properties)