当前位置:   article > 正文

Spark 【Spark SQL(一)DataFrame的创建、保存与基本操作】_spark dataframe

spark dataframe

前言

        今天学习Spark SQL,前面的RDD编程要想熟练还是得通过项目来熟练,所以先把Spark过一遍,后期针对不足的地方再加强,这样效率会更高一些。

简介

        在RDD编程中,我们使用的是SparkContext接口,接下来的Spark SQL中,我们使用到的是SparkSession接口。Spark2.0 出现的 SparkSession 接口替代了 Spark 1.6版本中的 SQLContext 和 HiveContext接口,来实现对数据的加载、转换、处理等功能。此外,SparkSession 封装了SparkContext、SparkConf 和 StreamingContext 等。

        也就是说,在Spark1.0 中,需要创建 SparkContext 对象用于 RDD编程 ,创建 SQLContext 对象用于 SQL 编程。而在Spqrk 2.x和3.x版本下,只需要创建一个 SparkSession 对象,就可以执行各种 Spark 操作。

        其实在我们的 spark-shell 中默认已经为我们提供了一个 SparkContext 对象(“sc”)和一个SparkSession 对象(“spark”)了。

        从Spark 2.x 开始,RDD被降级为底层的API,所有通过高层的 DataFrame API 表达的计算,都会被分解,生成优化好的底层的 RDD 操作,然后转化为Scala 字节码,交给执行器的JVM虚拟机。

结构化数据 DataFrame

        Spark SQL 所使用的数据抽象并非 RDD,而是 DataFrame。DataFrame 的推出,让 Spark具备了处理大规模结构化数据的能力。

DataFrame 概述

        DataFrame 是一种以 RDD 为基础的表格型的数据结构,提供了详细的结构信息,就相当于关系数据库中的一张表。

        和 RDD 一样,DataFrame 的操作也分为转换和行动操作,DataFrame 的计算过程也是“惰性”的,只有触发行动操作,Spark才会真正从头到尾进行一次计算。

入门案例

给定一组键值对(书名,销量),现在求每个键对应的平均值,也就是图书的平均销量。

  1. def main(args: Array[String]): Unit = {
  2. //创建SparkSession对象
  3. val spark: SparkSession = SparkSession.builder()
  4. .master("local[*]")
  5. .appName("test01")
  6. .getOrCreate()
  7. val df: DataFrame = spark.createDataFrame(Array(("spark", 2), ("hadoop", 5), ("spark", 3), ("hadoop", 6)))
  8. .toDF("book", "amount")
  9. val df2: DataFrame = df.groupBy("book") agg(avg("amount"))
  10. df2.show()
  11. spark.stop()
  12. }

运行结果:

  1. +------+-----------+
  2. | book|avg(amount)|
  3. +------+-----------+
  4. | spark| 2.5|
  5. |hadoop| 5.5|
  6. +------+-----------+

DataFrame 的创建与保存

Spark SQL 支持多种数据源创建 DataFrame,也支持把 DataFrame 保存成各种数据格式。

1、Parquet

读取
  1. //1.第一种创建方式
  2. val df1 = spark.read.foramt("parquet").load("文件路径")
  3. //2.第二种创建方式
  4. val df2 = spark.read.parquet("文件路径")
保存
  1. //1.使用 Snappy 压缩算法压缩后输出
  2. df.write.foramt("parquet").mode("overwrite").option("compression","snappy").save("输出路径")
  3. //2.
  4. df.write.parquet("输出路径")

2、JSON

  1. //1.第一种创建方式
  2. val df1 = spark.read.foramt("json").load("文件路径")
  3. //2.第二种创建方式
  4. val df2 = spark.read.json("文件路径")
 保存
  1. df.write.format("json").mode("overwrite").save("输出路径")
  2. df.write.json("输出路径")

3、CSV

  1. //两种创建方式都需要定义数据模式
  2. val schema = "name:STRING,age INT,sex STRING"
  3. //1.第一种创建方式
  4. val df1 = spark.read.foramt("csv").schema(schema).option("header","true").option("seq",";").load("文件路径")
  5. //2.第二种创建方式
  6. val df2 = spark.read.schema(schema).option("header","true").option("seq",";").csv("文件地")
 保存
  1. //1.
  2. df.write().format("csv").mode("overwrite").save("输出路径")
  3. //2.
  4. df.write.csv("输出路径")

 4、文本文件

  1. //1.第一种创建方式
  2. val df1 = spark.read.foramt("text").load("文件路径")
  3. //2.第二种创建方式
  4. val df2 = spark.read.text("文件路径")
保存
  1. //1.
  2. df.write.text("输出路径")
  3. //2.
  4. df.write.foramt("text").save("输出路径")

集合类型

通过 SparkSession 对象调用 createDataFrame(集合) 方法。

  1. val df: DataFrame = spark.createDataFrame(Array(("spark", 2), ("hadoop", 5), ("spark", 3), ("hadoop", 6)))
  2. .toDF("book", "amount")

DataFrame 基本操作

我们将这个JSON文件作为输入源进行数据分析:

  1. {"name":"Michael", "age":30, "sex": "男"}
  2. {"name":"Andy", "age":19, "sex": "女"}
  3. {"name":"Justin", "age":19, "sex": "男"}
  4. {"name":"Bernadette", "age":20, "sex": "男"}
  5. {"name":"Gretchen", "age":23,"sex": "女"}
  6. {"name":"David", "age":27, "sex": "男"}
  7. {"name":"Joseph", "age":33,"sex": "女"}
  8. {"name":"Trish", "age":27,"sex": "女"}
  9. {"name":"Alex", "age":33,"sex": "女"}
  10. {"name":"Ben", "age":25, "sex": "男"}

 生成DataFrame对象:

val df: DataFrame = spark.read.json("data/sql/people.json")

在操作 DataFrame 时,有两种不同风格的语句,即DSL和SQL语句。但无论是执行 DSL 语句还是 SQL 语句,本质上都会被转换为对 RDD 的操作 。

DSL 语法

1、printSchema()

输出DataFrame对象的模式信息。

df.printSchema()

运行结果:

  1. root
  2. |-- _corrupt_record: string (nullable = true)
  3. |-- age: long (nullable = true)
  4. |-- name: string (nullable = true)
2、show()

 显示一个DataFrame的二维表格。

  1. //Scala中如果方法没有参数 括号可省略
  2. df.show()

运行结果: 

  1. +----------+----+
  2. | name| age|
  3. +----------+----+
  4. | null|null|
  5. | Michael| 30|
  6. | Andy| 19|
  7. | Justin| 19|
  8. |Bernadette| 25|
  9. | Gretchen| 23|
  10. | David| 27|
  11. | Joseph| 33|
  12. | Trish| 27|
  13. | Alex| 33|
  14. | Ben| 25|
  15. | null|null|
  16. +----------+----+
3、select()

从DataFrame中选取部分列的数据,还可以对列进行重命名,对某一列的值也可以统一进行操作(比如age都+1)。

  df.select(df("name").as("username"),df("age")+1).show()  //显示出DataFrame的name和age字段并将age字段的值都+1,将name用username代替

运行结果:

  1. +----------+---------+
  2. | username|(age + 1)|
  3. +----------+---------+
  4. | null| null|
  5. | Michael| 31|
  6. | Andy| 20|
  7. | Justin| 20|
  8. |Bernadette| 26|
  9. | Gretchen| 24|
  10. | David| 28|
  11. | Joseph| 34|
  12. | Trish| 28|
  13. | Alex| 34|
  14. | Ben| 26|
  15. | null| null|
  16. +----------+---------+
4、filter()

进行条件查询,找到满足条件要求的数据。

df.filter(df("age")>30).show()    //输出所有30岁以上的人的信息

运行结果:

  1. +------+---+
  2. | name|age|
  3. +------+---+
  4. |Joseph| 33|
  5. | Alex| 33|
  6. +------+---+
5、groupBy()

对记录进行分组。

 df.groupBy(df("sex")).count().show()

运行结果:

  1. +----+-----+
  2. | sex|count|
  3. +----+-----+
  4. | 男| 3|
  5. | 女| 4|
  6. +----+-----+
6、sort()

根据某一字段进行升序(asc)或降序排列(desc)。

  df.select(df("name"),df("age"),df("sex")).sort(df("age").desc,df("name").asc).show()  //先根据age降序排列 age相同根据name升序排列

运行结果:

  1. +----------+----+----+
  2. | name| age| sex|
  3. +----------+----+----+
  4. | Alex| 33| 女|
  5. | Joseph| 33| 女|
  6. | Michael| 30| 男|
  7. | David| 27| 男|
  8. | Trish| 27| 女|
  9. | Ben| 25| 男|
  10. | Gretchen| 23| 女|
  11. |Bernadette| 20| 男|
  12. | Andy| 19| 女|
  13. | Justin| 19| 男|
  14. | null|null|null|
  15. | null|null|null|
  16. +----------+----+----+

7、withColumn()

用于为 DataFrame 增加一个新的列。

  1. //新增一列 isYoung 如果age>25 为young 否则为 old
  2. df.select(df("name"),df("age"),df("sex")).withColumn("isYoung",when(df("age")>25,"young").otherwise("old")).show()

运行结果:

  1. +----------+----+----+-------+
  2. | name| age| sex|isYoung|
  3. +----------+----+----+-------+
  4. | null|null|null| old|
  5. | Michael| 30| 男| old|
  6. | Andy| 19| 女| young|
  7. | Justin| 19| 男| young|
  8. |Bernadette| 20| 男| young|
  9. | Gretchen| 23| 女| young|
  10. | David| 27| 男| old|
  11. | Joseph| 33| 女| old|
  12. | Trish| 27| 女| old|
  13. | Alex| 33| 女| old|
  14. | Ben| 25| 男| old|
  15. | null|null|null| old|
  16. +----------+----+----+-------+
8、drop()

        可以删除DataFrame中的一列,上面我们是直接在 DataFrame对象的基础上进行查询并展示,show() 方法并不会有返回对象,但其实其它操作(比如select、withColumn、filter、sort等)都会返回一个新的 DataFrame对象,相当于一张新的二维表格。同样,drop() 后会返回一个新的 DataFrame 对象,相当于删除某列后的新表。

  1. val df: DataFrame = spark.read.json("data/sql/people.json")
  2. val df2: DataFrame = df.select(df("name"), df("age"), df("sex")).withColumn("isYoung", when(df("age") < 25, "young").otherwise("old"))
  3. val df3: DataFrame = df2.drop(df("isYoung"))
  4. df3.show()

9、其它操作

        除此之外,还有其它一些操作比如min()、max()、sum()和avg()等,比较简单,用的时候再学。

SQL 语法

        相比较 DSL 语句,SQL 语句徐需要在执行 SQL 语句之前先创建一张临时表,因为毕竟SQL语句本来就是对关系型表进行操作的语句,所以我们的数据源需要先通过createTempView()或createOrReplaceTempView()方法转换为临时表。

        这两个方法没太大区别,只不过createOrReplaceTempView()会判断是否已存在这么张表,如果存在同名的表,就用新表替换掉。而createTempView()的话,如果已经存在同名的表,它就会报错。

我们继续使用上面的 people.json 文件进行操作。

SQL 案例1
  1. //通过JSON文件创建 DataFrame 对象
  2. val df = spark.read.format("json").load("data/sql/people.json")
  3. //创建临时表 不需要返回值
  4. df.createTempView("people")
  5. spark.sql("SELECT * FROM PEOPLE").show()

运行结果:

  1. +---+----------+---+
  2. |age| name|sex|
  3. +---+----------+---+
  4. | 30| Michael| 男|
  5. | 19| Andy| 女|
  6. | 19| Justin| 男|
  7. | 20|Bernadette| 男|
  8. | 23| Gretchen| 女|
  9. | 27| David| 男|
  10. | 33| Joseph| 女|
  11. | 27| Trish| 女|
  12. | 33| Alex| 女|
  13. | 25| Ben| 男|
  14. +---+----------+---+

SQL 案例2 

统计男女人数。

 spark.sql("SELECT sex,COUNT(*) AS nums FROM people group by sex").show()

注意AS 后面的新字段名不能带引号,不能是中文!

运行结果:

  1. +---+----+
  2. |sex|nums|
  3. +---+----+
  4. | 男| 4|
  5. | 女| 6|
  6. +---+----+
SQL 函数

Spark SQL 提供了200多个函数供用户选择,涵盖了大部分的日常应用场景。此外,用户也可以自定义函数。

案例:

        假设一张用户信息表中有 name、age、create_time 这3列数据,这里要求使用Spark的系统函数 from_unixtime(),将时间戳类型的 create_time 格式化成时间字符串,然后使用自定义的函数将用户名转为大写英文字母。

  1. def main(args: Array[String]): Unit = {
  2. val spark = SparkSession.builder()
  3. .master("local")
  4. .appName("spark func")
  5. .getOrCreate()
  6. val schema: StructType = StructType(List(StructField("name", StringType, true),
  7. StructField("age", IntegerType, true),
  8. StructField("create_time", LongType, true)
  9. ))
  10. val javaList:util.ArrayList[Row] = new util.ArrayList[Row]()
  11. javaList.add(Row("XiaoMei",24,System.currentTimeMillis()/1000))
  12. javaList.add(Row("XiaoShuai",23,System.currentTimeMillis()/1000))
  13. javaList.add(Row("XiaoLiu",21,System.currentTimeMillis()/1000))
  14. javaList.add(Row("XiaoMa",21,System.currentTimeMillis()/1000))
  15. val df = spark.createDataFrame(javaList, schema)
  16. df.show()
  17. df.createTempView("student")
  18. spark.sql("SELECT name,age,from_unixtime(create_time,'yyyy-MM-dd HH:mm:ss') FROM student").show()
  19. //注册一个新的用户自定义函数
  20. spark.udf.register("toUpperCaseUDF",(column:String)=>column.toUpperCase)
  21. //调用自定义函数
  22. spark.sql("SELECT toUpperCaseUDF(name) AS name,age,from_unixtime(create_time,'yyyy-MM-dd HH:mm:ss') AS create_time FROM student").show()
  23. spark.stop()
  24. }

运行结果:

默认查询结果:

  1. +---------+---+-----------+
  2. | name|age|create_time|
  3. +---------+---+-----------+
  4. | XiaoMei| 24| 1694256797|
  5. |XiaoShuai| 23| 1694256797|
  6. | XiaoLiu| 21| 1694256797|
  7. | XiaoMa| 21| 1694256797|
  8. +---------+---+-----------+

调用from_unixtime()函数:

  1. +---------+---+-----------------------------------------------+
  2. | name|age|from_unixtime(create_time, yyyy-MM-dd HH:mm:ss)|
  3. +---------+---+-----------------------------------------------+
  4. | XiaoMei| 24| 2023-09-09 18:53:17|
  5. |XiaoShuai| 23| 2023-09-09 18:53:17|
  6. | XiaoLiu| 21| 2023-09-09 18:53:17|
  7. | XiaoMa| 21| 2023-09-09 18:53:17|
  8. +---------+---+-----------------------------------------------+

使用自定义函数:

  1. +---------+---+-------------------+
  2. | name|age| create_time|
  3. +---------+---+-------------------+
  4. | XIAOMEI| 24|2023-09-09 18:53:17|
  5. |XIAOSHUAI| 23|2023-09-09 18:53:17|
  6. | XIAOLIU| 21|2023-09-09 18:53:17|
  7. | XIAOMA| 21|2023-09-09 18:53:17|
  8. +---------+---+-------------------+

总结

        今天就写到这里,明天周日继续努力,今天我新开了章节-Spark SQL,我学习了Spark SQL中一个重要的抽象数据结构-DataFrame,学习了DataFrame的成绩以及保存,还有DataFrame的两张操作方式:DSL语句和SQL语句。

        至于书上提到的 StructType、StructFeild 明天好好研究一下。

        

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/693347
推荐阅读
相关标签
  

闽ICP备14008679号