• Spark 【Spark SQL(一)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 明天好好研究一下。

            

  • 相关阅读:
    golang的循环引用解决方法
    【ARFoundation学习笔记】点云与参考点
    作为产品经理,在工作中常逛哪些网站?
    华为OD机试 - 疫情扩散时间计算 - 矩阵(Java 2024 C卷 200分)
    Radiology 谈人工智能在放射学领域的10个预测方向 [文献阅读]
    单一职责原则 (Single Responsibility Principle)
    2021年下半年软件设计师下午真题答案及解析(五)
    SpringBoot+MinIO(三)
    十一、做高并发内存池项目过程中遇到的bug以及调试bug的方法和心得
    C++11 右值引用核心
  • 原文地址:https://blog.csdn.net/m0_64261982/article/details/132778503