Spark SQL入门到精通之第一篇Dataset的基本操做

浪尖准备花一个月时间给大加分享一系列的Spark SQL从入门到精通的文章。文章目录大致如下:

  1. Spark SQL的Dataset基本操作
  2. Spark SQL的Dataset复杂操作
  3. Spark SQL的SQL操作
  4. Spark SQL的thriftServer的使用
  5. Spark SQL分区特性,数据倾斜及调优
  6. Spark SQL的执行计划解析
  7. 数据源讲解及自定义数据源
  8. 自定义Spark SQL的优化器
  9. 自定义Spark SQL的执行计划

当然,中间可能稍微有变动和优化扩展,最终还是希望通过该系列文章帮助大家完全掌握Spark SQL。

本文第一弹,Spark SQL的入门篇,Dataset的基本操作。

本系列文章是以Spark 2.3.1,hadoop-2.7.4,jdk1.8为例讲解。

0.基本简介和准备工作

Dataset接口是在spark 1.6引入的,受益于RDD(强类型,可以使用强大的lambda函数),同时也可以享受Spark SQL优化执行引擎的优点。Dataset的可以从jvm 对象创建,然后就可以使用转换函数(map,flatmap,filter等)。

1.6版本之前常用的Dataframe是一种特殊的Dataset,也即是

type DataFrame = Dataset[Row]

Dataframe就像传统数据库的一张表,数据源比较丰富:

结构化数据文件(json,csv,orc等),hive表,外部数据库,已有RDD。

下面进行测试,大家可能都会说缺少数据,实际上Spark源码里跟我们提供了丰富的测试数据。源码的examples路径下:examples/src/main/resources。

首先要创建一个SparkSession

val sparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[*]") .set("yarn.resourcemanager.hostname", "localhost") 
//executor的实例数
.set("spark.executor.instances","2") 
.set("spark.default.parallelism","4") 
//sql shuffle的并行度,由于是本地测试,所以设置较小值,避免产生过多空task,实际上要根据生产数据量进行设置。
.set("spark.sql.shuffle.partitions","4") .setJars(List("/Users/meitu/Desktop/sparkjar/bigdata.jar" 
,"/opt/jars/spark-streaming-kafka-0-10_2.11-2.3.1.jar" 
,"/opt/jars/kafka-clients-0.10.2.2.jar" 
 ,"/opt/jars/kafka_2.11-0.10.2.2.jar"
))
val spark = SparkSession 
.builder() 
.config(sparkConf) 
.getOrCreate()

创建dataset

val sales = spark.createDataFrame(
Seq( ("Warsaw", 2016, 100), ("Warsaw", 2017, 200), ("Warsaw", 2015, 100), ("Warsaw", 2017, 200), ("Beijing", 2017, 200), ("Beijing", 2016, 200),
 ("Beijing", 2015, 200), ("Beijing", 2014, 200), ("Warsaw", 2014, 200),
 ("Boston", 2017, 50), ("Boston", 2016, 50), ("Boston", 2015, 50),
 ("Boston", 2014, 150)))
.toDF("city", "year", "amount")

使用函数的时候要导入包:

import org.apache.spark.sql.functions.{col,expr}

1. select

列名称可以是字符串,这种形式无法对列名称使用表达式进行逻辑操作。

使用col函数,可以直接对列进行一些逻辑操作。

sales.select("city","year","amount").show(1)
sales.select(col("city"),col("amount")+1).show(1)

2. selectExpr

参数是字符串,且直接可以使用表达式。

也可以使用select+expr函数来替代。

sales.selectExpr("city","year as date","amount+1").show(10)
sales.select(expr("city"),expr("year as date"),expr("amount+1")).show(10)

3.filter

参数可以是与col结合的表达式,参数类型为row返回值为boolean的函数,字符串表达式。

sales.filter(col("amount")>150).show()
sales.filter(row=>{ row.getInt(2)>150}).show(10)
sales.filter("amount > 150 ").show(10)

4.where

类似于fliter,参数可以是与col函数结合的表达式也可以是直接使用表达式字符串。

sales.where(col("amount")>150).show()
sales.where("amount > 150 ").show()

5.group by

主要是以count和agg聚合函数为例讲解groupby函数

sales.groupBy("city").count().show(10)
sales.groupBy(col("city")).agg(sum("amount").as("total")).show(10)

6.union

两个dataset的union操作这个等价于union all操作,所以要实现传统数据库的union操作,需要在其后使用distinct进行去重操作。

sales.union(sales).groupBy("city").count().show()

7.join

join操作相对比较复杂,具体如下:

/*相同的列进行join*/ 
 sales.join(sales,"city").show(10)
 /*多列join*/ 
sales.join(sales,Seq("city","year")).show() 
/* 指定join类型, join 类型可以选择: 
 `inner`, `cross`, `outer`, `full`, `full_outer`, 
`left`, `left_outer`, `right`, `right_outer`, 
`left_semi`, `left_anti`. 
 */ 
// 内部join
sales.join(sales,Seq("city","year"),"inner").show() 
/* join条件 :
可以在join方法里放入join条件,
也可以使用where,这两种情况都要求字段名称不一样。
*/ 
sales.join(sales, col("city").alias("city1") === col("city")).show() sales.join(sales).where(col("city").alias("city1") === col("city")).show() 
 /* 
dataset的self join 此处使用where作为条件,
需要增加配置.set("spark.sql.crossJoin.enabled","true") 
也可以加第三个参数,join类型,可以选择如下: 
`inner`, `cross`, `outer`, `full`, `full_outer`, `left`,
 `left_outer`, `right`, `right_outer`, `left_semi`, `left_anti` 
 */ 
sales.join(sales,sales("city") === sales("city")).show() sales.join(sales).where(sales("city") === sales("city")).show()
/* joinwith,可以指定第三个参数,join类型,
类型可以选择如下:
 `inner`, `cross`, `outer`, `full`, `full_outer`, `left`, `left_outer`, 
 `right`, `right_outer`。 
 */ 
sales.joinWith(sales,sales("city") === sales("city"),"inner").show() 
输出结果: 

8.order by

orderby 全局有序,其实用的还是sort 
sales.orderBy(col("year").desc,col("amount").asc).show()
sales.orderBy("city","year").show()

sort or orderby

9.sort

全局排序,直接替换掉8小结的orderby即可。

10.sortwithinpartition

在分区内部进行排序,局部排序

sales.sortWithinPartitions(col("year").desc,col("amount").asc).show()
sales.sortWithinPartitions("city","year").show()

可以看到,city为背景的应该是分配到不同的分区,然后每个分区内部year都是有序的。

sortWithinPartition

11.withColumn

/* withColumn 
假如列,存在就替换,不存在新增 
withColumnRenamed 
对已有的列进行重命名
 */
//相当于给原来amount列,+1
sales.withColumn("amount",col("amount")+1).show()
// 对amount列+1,然后将值增加到一个新列 amount1
sales.withColumn("amount1",col("amount")+1).show()
// 将amount列名,修改为amount1
sales.withColumnRenamed("amount","amount1").show()

12.foreach

这个跟rdd的foreach一样,元素类型是row。

sales.foreach(row=>{ 
println(row.getString(0))
})

13.foreachPartition

跟RDD的foreachPartition一样,针对分区进行计算,对于输出到数据库,kafka等数据相对于使用foreach可以大量减少连接数。

sales.foreachPartition(partition=>{ 
 //打开数据库链接等 
 partition.foreach(each=>{ 
 println(each.getString(0))
 //插入数据库 
 })
 //关闭数据库链接 
})

13.distinct

针对dataset的行去重,返回的是所有行都不重复的dataset。

sales.distinct().show(10)

14.dropDuplicates

这个适用于dataset有唯一的主键,然后对主键进行去重。

val before = sales.count()
val after = sales.dropDuplicates("city").count()
println("before ====> " +before)
println("after ====> "+after)

去重

15.drop

删除一列,或者多列,这是一个变参数算子。

sales.drop("city").show()
打印出来schema信息如下:
root
|-- year: integer (nullable = false)
|-- amount: integer (nullable = false)

16. printSchema

输出dataset的schema信息

sales.printSchema()

输出结果如下:

root

|-- city: string (nullable = true)

|-- year: integer (nullable = false)

|-- amount: integer (nullable = false)

17.explain()

打印执行计划,这个便于调试,了解spark sql引擎的优化执行的整个过程

sales.orderBy(col("year").desc,col("amount").asc).explain()
执行计划输出如下:
== Physical Plan ==
*(1) Sort [year#7 DESC NULLS LAST, amount#8 ASC NULLS FIRST], true, 0
+- Exchange rangepartitioning(year#7 DESC NULLS LAST, amount#8 ASC NULLS FIRST, 3)
+- LocalTableScan [city#6, year#7, amount#8]

这基本上涵盖了Spark SQL的Dataset接口的基本操作了,希望帮助出初学者快速了解spark。

举报
评论 0