赞
踩
今天学习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虚拟机。
Spark SQL 所使用的数据抽象并非 RDD,而是 DataFrame。DataFrame 的推出,让 Spark具备了处理大规模结构化数据的能力。
DataFrame 是一种以 RDD 为基础的表格型的数据结构,提供了详细的结构信息,就相当于关系数据库中的一张表。
和 RDD 一样,DataFrame 的操作也分为转换和行动操作,DataFrame 的计算过程也是“惰性”的,只有触发行动操作,Spark才会真正从头到尾进行一次计算。
给定一组键值对(书名,销量),现在求每个键对应的平均值,也就是图书的平均销量。
- def main(args: Array[String]): Unit = {
- //创建SparkSession对象
- val spark: SparkSession = SparkSession.builder()
- .master("local[*]")
- .appName("test01")
- .getOrCreate()
-
- val df: DataFrame = spark.createDataFrame(Array(("spark", 2), ("hadoop", 5), ("spark", 3), ("hadoop", 6)))
- .toDF("book", "amount")
- val df2: DataFrame = df.groupBy("book") agg(avg("amount"))
-
- df2.show()
- spark.stop()
- }
运行结果:
- +------+-----------+
- | book|avg(amount)|
- +------+-----------+
- | spark| 2.5|
- |hadoop| 5.5|
- +------+-----------+
Spark SQL 支持多种数据源创建 DataFrame,也支持把 DataFrame 保存成各种数据格式。
- //1.第一种创建方式
- val df1 = spark.read.foramt("parquet").load("文件路径")
- //2.第二种创建方式
- val df2 = spark.read.parquet("文件路径")
- //1.使用 Snappy 压缩算法压缩后输出
- df.write.foramt("parquet").mode("overwrite").option("compression","snappy").save("输出路径")
- //2.
- df.write.parquet("输出路径")
- //1.第一种创建方式
- val df1 = spark.read.foramt("json").load("文件路径")
- //2.第二种创建方式
- val df2 = spark.read.json("文件路径")
- df.write.format("json").mode("overwrite").save("输出路径")
- df.write.json("输出路径")
- //两种创建方式都需要定义数据模式
- val schema = "name:STRING,age INT,sex STRING"
- //1.第一种创建方式
- val df1 = spark.read.foramt("csv").schema(schema).option("header","true").option("seq",";").load("文件路径")
- //2.第二种创建方式
- val df2 = spark.read.schema(schema).option("header","true").option("seq",";").csv("文件地")
- //1.
- df.write().format("csv").mode("overwrite").save("输出路径")
- //2.
- df.write.csv("输出路径")
- //1.第一种创建方式
- val df1 = spark.read.foramt("text").load("文件路径")
- //2.第二种创建方式
- val df2 = spark.read.text("文件路径")
- //1.
- df.write.text("输出路径")
- //2.
- df.write.foramt("text").save("输出路径")
通过 SparkSession 对象调用 createDataFrame(集合) 方法。
- val df: DataFrame = spark.createDataFrame(Array(("spark", 2), ("hadoop", 5), ("spark", 3), ("hadoop", 6)))
- .toDF("book", "amount")
我们将这个JSON文件作为输入源进行数据分析:
- {"name":"Michael", "age":30, "sex": "男"}
- {"name":"Andy", "age":19, "sex": "女"}
- {"name":"Justin", "age":19, "sex": "男"}
- {"name":"Bernadette", "age":20, "sex": "男"}
- {"name":"Gretchen", "age":23,"sex": "女"}
- {"name":"David", "age":27, "sex": "男"}
- {"name":"Joseph", "age":33,"sex": "女"}
- {"name":"Trish", "age":27,"sex": "女"}
- {"name":"Alex", "age":33,"sex": "女"}
- {"name":"Ben", "age":25, "sex": "男"}
生成DataFrame对象:
val df: DataFrame = spark.read.json("data/sql/people.json")
在操作 DataFrame 时,有两种不同风格的语句,即DSL和SQL语句。但无论是执行 DSL 语句还是 SQL 语句,本质上都会被转换为对 RDD 的操作 。
输出DataFrame对象的模式信息。
df.printSchema()
运行结果:
- root
- |-- _corrupt_record: string (nullable = true)
- |-- age: long (nullable = true)
- |-- name: string (nullable = true)
显示一个DataFrame的二维表格。
- //Scala中如果方法没有参数 括号可省略
- df.show()
运行结果:
- +----------+----+
- | name| age|
- +----------+----+
- | null|null|
- | Michael| 30|
- | Andy| 19|
- | Justin| 19|
- |Bernadette| 25|
- | Gretchen| 23|
- | David| 27|
- | Joseph| 33|
- | Trish| 27|
- | Alex| 33|
- | Ben| 25|
- | null|null|
- +----------+----+
从DataFrame中选取部分列的数据,还可以对列进行重命名,对某一列的值也可以统一进行操作(比如age都+1)。
df.select(df("name").as("username"),df("age")+1).show() //显示出DataFrame的name和age字段并将age字段的值都+1,将name用username代替
运行结果:
- +----------+---------+
- | username|(age + 1)|
- +----------+---------+
- | null| null|
- | Michael| 31|
- | Andy| 20|
- | Justin| 20|
- |Bernadette| 26|
- | Gretchen| 24|
- | David| 28|
- | Joseph| 34|
- | Trish| 28|
- | Alex| 34|
- | Ben| 26|
- | null| null|
- +----------+---------+
进行条件查询,找到满足条件要求的数据。
df.filter(df("age")>30).show() //输出所有30岁以上的人的信息
运行结果:
- +------+---+
- | name|age|
- +------+---+
- |Joseph| 33|
- | Alex| 33|
- +------+---+
对记录进行分组。
df.groupBy(df("sex")).count().show()
运行结果:
- +----+-----+
- | sex|count|
- +----+-----+
- | 男| 3|
- | 女| 4|
- +----+-----+
根据某一字段进行升序(asc)或降序排列(desc)。
df.select(df("name"),df("age"),df("sex")).sort(df("age").desc,df("name").asc).show() //先根据age降序排列 age相同根据name升序排列
运行结果:
- +----------+----+----+
- | name| age| sex|
- +----------+----+----+
- | Alex| 33| 女|
- | Joseph| 33| 女|
- | Michael| 30| 男|
- | David| 27| 男|
- | Trish| 27| 女|
- | Ben| 25| 男|
- | Gretchen| 23| 女|
- |Bernadette| 20| 男|
- | Andy| 19| 女|
- | Justin| 19| 男|
- | null|null|null|
- | null|null|null|
- +----------+----+----+
用于为 DataFrame 增加一个新的列。
- //新增一列 isYoung 如果age>25 为young 否则为 old
- df.select(df("name"),df("age"),df("sex")).withColumn("isYoung",when(df("age")>25,"young").otherwise("old")).show()
运行结果:
- +----------+----+----+-------+
- | name| age| sex|isYoung|
- +----------+----+----+-------+
- | null|null|null| old|
- | Michael| 30| 男| old|
- | Andy| 19| 女| young|
- | Justin| 19| 男| young|
- |Bernadette| 20| 男| young|
- | Gretchen| 23| 女| young|
- | David| 27| 男| old|
- | Joseph| 33| 女| old|
- | Trish| 27| 女| old|
- | Alex| 33| 女| old|
- | Ben| 25| 男| old|
- | null|null|null| old|
- +----------+----+----+-------+
可以删除DataFrame中的一列,上面我们是直接在 DataFrame对象的基础上进行查询并展示,show() 方法并不会有返回对象,但其实其它操作(比如select、withColumn、filter、sort等)都会返回一个新的 DataFrame对象,相当于一张新的二维表格。同样,drop() 后会返回一个新的 DataFrame 对象,相当于删除某列后的新表。
- val df: DataFrame = spark.read.json("data/sql/people.json")
- val df2: DataFrame = df.select(df("name"), df("age"), df("sex")).withColumn("isYoung", when(df("age") < 25, "young").otherwise("old"))
- val df3: DataFrame = df2.drop(df("isYoung"))
- df3.show()
除此之外,还有其它一些操作比如min()、max()、sum()和avg()等,比较简单,用的时候再学。
相比较 DSL 语句,SQL 语句徐需要在执行 SQL 语句之前先创建一张临时表,因为毕竟SQL语句本来就是对关系型表进行操作的语句,所以我们的数据源需要先通过createTempView()或createOrReplaceTempView()方法转换为临时表。
这两个方法没太大区别,只不过createOrReplaceTempView()会判断是否已存在这么张表,如果存在同名的表,就用新表替换掉。而createTempView()的话,如果已经存在同名的表,它就会报错。
我们继续使用上面的 people.json 文件进行操作。
- //通过JSON文件创建 DataFrame 对象
- val df = spark.read.format("json").load("data/sql/people.json")
-
- //创建临时表 不需要返回值
- df.createTempView("people")
-
- spark.sql("SELECT * FROM PEOPLE").show()
运行结果:
-
- +---+----------+---+
- |age| name|sex|
- +---+----------+---+
- | 30| Michael| 男|
- | 19| Andy| 女|
- | 19| Justin| 男|
- | 20|Bernadette| 男|
- | 23| Gretchen| 女|
- | 27| David| 男|
- | 33| Joseph| 女|
- | 27| Trish| 女|
- | 33| Alex| 女|
- | 25| Ben| 男|
- +---+----------+---+
统计男女人数。
spark.sql("SELECT sex,COUNT(*) AS nums FROM people group by sex").show()
注意:AS 后面的新字段名不能带引号,不能是中文!
运行结果:
- +---+----+
- |sex|nums|
- +---+----+
- | 男| 4|
- | 女| 6|
- +---+----+
Spark SQL 提供了200多个函数供用户选择,涵盖了大部分的日常应用场景。此外,用户也可以自定义函数。
假设一张用户信息表中有 name、age、create_time 这3列数据,这里要求使用Spark的系统函数 from_unixtime(),将时间戳类型的 create_time 格式化成时间字符串,然后使用自定义的函数将用户名转为大写英文字母。
- def main(args: Array[String]): Unit = {
- val spark = SparkSession.builder()
- .master("local")
- .appName("spark func")
- .getOrCreate()
-
- val schema: StructType = StructType(List(StructField("name", StringType, true),
- StructField("age", IntegerType, true),
- StructField("create_time", LongType, true)
- ))
-
- val javaList:util.ArrayList[Row] = new util.ArrayList[Row]()
- javaList.add(Row("XiaoMei",24,System.currentTimeMillis()/1000))
- javaList.add(Row("XiaoShuai",23,System.currentTimeMillis()/1000))
- javaList.add(Row("XiaoLiu",21,System.currentTimeMillis()/1000))
- javaList.add(Row("XiaoMa",21,System.currentTimeMillis()/1000))
-
- val df = spark.createDataFrame(javaList, schema)
- df.show()
-
- df.createTempView("student")
-
- spark.sql("SELECT name,age,from_unixtime(create_time,'yyyy-MM-dd HH:mm:ss') FROM student").show()
-
- //注册一个新的用户自定义函数
- spark.udf.register("toUpperCaseUDF",(column:String)=>column.toUpperCase)
- //调用自定义函数
- spark.sql("SELECT toUpperCaseUDF(name) AS name,age,from_unixtime(create_time,'yyyy-MM-dd HH:mm:ss') AS create_time FROM student").show()
-
- spark.stop()
- }
运行结果:
默认查询结果:
- +---------+---+-----------+
- | name|age|create_time|
- +---------+---+-----------+
- | XiaoMei| 24| 1694256797|
- |XiaoShuai| 23| 1694256797|
- | XiaoLiu| 21| 1694256797|
- | XiaoMa| 21| 1694256797|
- +---------+---+-----------+
调用from_unixtime()函数:
- +---------+---+-----------------------------------------------+
- | name|age|from_unixtime(create_time, yyyy-MM-dd HH:mm:ss)|
- +---------+---+-----------------------------------------------+
- | XiaoMei| 24| 2023-09-09 18:53:17|
- |XiaoShuai| 23| 2023-09-09 18:53:17|
- | XiaoLiu| 21| 2023-09-09 18:53:17|
- | XiaoMa| 21| 2023-09-09 18:53:17|
- +---------+---+-----------------------------------------------+
使用自定义函数:
- +---------+---+-------------------+
- | name|age| create_time|
- +---------+---+-------------------+
- | XIAOMEI| 24|2023-09-09 18:53:17|
- |XIAOSHUAI| 23|2023-09-09 18:53:17|
- | XIAOLIU| 21|2023-09-09 18:53:17|
- | XIAOMA| 21|2023-09-09 18:53:17|
- +---------+---+-------------------+
今天就写到这里,明天周日继续努力,今天我新开了章节-Spark SQL,我学习了Spark SQL中一个重要的抽象数据结构-DataFrame,学习了DataFrame的成绩以及保存,还有DataFrame的两张操作方式:DSL语句和SQL语句。
至于书上提到的 StructType、StructFeild 明天好好研究一下。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。