Spark SQL入门到精通之第一篇Dataset的基本操做
浪尖准备花一个月时间给大加分享一系列的Spark SQL从入门到精通的文章。文章目录大致如下:
- Spark SQL的Dataset基本操作
- Spark SQL的Dataset复杂操作
- Spark SQL的SQL操作
- Spark SQL的thriftServer的使用
- Spark SQL分区特性,数据倾斜及调优
- Spark SQL的执行计划解析
- 数据源讲解及自定义数据源
- 自定义Spark SQL的优化器
- 自定义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()
9.sort
全局排序,直接替换掉8小结的orderby即可。
10.sortwithinpartition
在分区内部进行排序,局部排序
sales.sortWithinPartitions(col("year").desc,col("amount").asc).show() sales.sortWithinPartitions("city","year").show()
可以看到,city为背景的应该是分配到不同的分区,然后每个分区内部year都是有序的。
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。
请先 后发表评论~