Spark-SQL
创建DataFrame
创建DataFrame首先需要SparkSession对象,创建方式:
val spark = SparkSession.builder()
.master("local[*]")
.appName("SparkSQL")
.getOrCreate()
创建完成后,通过SparkSession
对象调用createDataFrame()
方法创建DataFrame
DataFrame可以使用以下方式创建:
- 使用RDD/Seq/List转换,可以在转化时指定列名,注意,要导入
SparkSession
对象的隐式转换,否则无法调用toDF
函数
val conf = new SparkConf().setMaster("local[*]").setAppName("createDF")
val spark = SparkSession.builder().config(conf).getOrCreate()
val sc = spark.sparkContext
import spark.implicits._
val rdd = sc.makeRDD(Seq((1, "a"), (2, "b"), (3, "c")))
val dataFrame = rdd.toDF("id", "value")
dataFrame.printSchema()
dataFrame.show()
/*
root
|-- id: integer (nullable = false)
|-- value: string (nullable = true)
+---+-----+
| id|value|
+---+-----+
| 1| a|
| 2| b|
| 3| c|
+---+-----+
如果不指定列明,则会按照列的排序,以_1,_2,。。。。来命名列
如果指定列名时列明的数量和实际列的数量不一致,则会抛出异常
*/
- 使用
SparkSession
对象的createDataFrame
函数创建,该方法首先传入一个RDD对象作为数据源,再传入一个schema作为表结构
val conf = new SparkConf().setMaster("local[*]").setAppName("createDF")
val spark = SparkSession.builder().config(conf).getOrCreate()
val sc = spark.sparkContext
import spark.implicits._
val rdd = sc.makeRDD(Seq((1, "zs"), (2, "ls"), (3, "ww")))
val schema = StructType(StructField("id", IntegerType) :: StructField("name", StringType) :: Nil)
// 需要注意,createDataFrame只接受RDD[org.apache.spark.sql.Row]类型的RDD
val dataFrame = spark.createDataFrame(rdd.map(e => Row(e._1, e._2)), schema)
dataFrame.printSchema()
dataFrame.show()
- 从文件中读取,通过
SparkSession
对象调用read
函数,可以获得一个DataFrameReader
,DataFrameReader
类中定义了从各种类型的文件中读取数据的方法,例如:jdbc、json、csv、textFile等,还可以调用format
函数自行指定,下面演示一个从csv文件中读取数据案例:
要读取的目标文件格式:
val conf = new SparkConf().setMaster("local[*]").setAppName("createDF")
val spark = SparkSession.builder().config(conf).getOrCreate()
val sc = spark.sparkContext
val dataFrame = spark.read.format("csv").option("header","true").load("in/event_attendees.csv")
dataFrame.printSchema()
dataFrame.show(5)
/*
root
|-- event: string (nullable = true)
|-- yes: string (nullable = true)
|-- maybe: string (nullable = true)
|-- invited: string (nullable = true)
|-- no: string (nullable = true)
+----------+--------------------+--------------------+--------------------+--------------------+
| event| yes| maybe| invited| no|
+----------+--------------------+--------------------+--------------------+--------------------+
|1159822043|1975964455 252302...|2733420590 517546...|1723091036 379587...|3575574655 107729...|
| 686467261|2394228942 268611...|1498184352 645689...|1788073374 733302...| null|
|1186208412| null|3320380166 381079...|1379121209 440668682|1728988561 295072...|
|2621578336| null| null| null| null|
| 855842686|2406118796 355089...|2671721559 176144...|1518670705 880919...| 3500235232|
+----------+--------------------+--------------------+--------------------+--------------------+
only showing top 5 rows
*/
RDD、DataFrame和DataSet三者是可以互相转换的,三者间的区别和联系这里就不详细介绍了,熟练运用三者的函数并灵活转换可以明显提高开发效率
使用DataFrame
创建视图
使用上面创建好的DataFrame对象,有四个函数可以创建视图:
createGlobalTempView # 创建全局视图
createTempView # 创建临时视图
createOrReplaceGlobalTempView # 创建全局视图,如果视图已存在则替换
createOrReplaceTempView # 创建临时视图,如果视图已存在则替换
推荐创建临时视图(临时视图指创建的视图仅存在于创建该视图的session中,当session关闭时,临时视图也会随之被清理)
视图创建好后,就可以使用SparkSession
对象调用sql()
函数,执行SQL语句了:
val conf = new SparkConf().setMaster("local[*]").setAppName("createDF")
val spark = SparkSession.builder().config(conf).getOrCreate()
val sc = spark.sparkContext
val dataFrame:DataFrame = spark.read.format("csv").option("header","true").load("in/event_attendees.csv")
dataFrame.createTempView("user_attend")
spark.sql(
"""
|select * from user_attend limit 5
""".stripMargin).show()
/*
+----------+--------------------+--------------------+--------------------+--------------------+
| event| yes| maybe| invited| no|
+----------+--------------------+--------------------+--------------------+--------------------+
|1159822043|1975964455 252302...|2733420590 517546...|1723091036 379587...|3575574655 107729...|
| 686467261|2394228942 268611...|1498184352 645689...|1788073374 733302...| null|
|1186208412| null|3320380166 381079...|1379121209 440668682|1728988561 295072...|
|2621578336| null| null| null| null|
| 855842686|2406118796 355089...|2671721559 176144...|1518670705 880919...| 3500235232|
+----------+--------------------+--------------------+--------------------+--------------------+
*/
处理数据的三种风格
DataFrame对象可以调用RDD的函数处理数据,也可以创建表视图后,使用SQL语句处理数据。同时DataFrame还可以单独调用类SQL的函数处理数据。
以求取各科成绩top3为例,演示这三种编程风格:
源数据(简化案例,数据以经过处理):
sid,cid,score
01,01,80
01,02,90
01,03,99
02,01,70
02,02,60
02,03,80
03,01,80
03,02,80
03,03,80
04,01,50
04,02,30
04,03,20
05,01,76
05,02,87
06,01,31
06,03,34
07,02,89
07,03,98
首先加载数据
val conf = new SparkConf().setMaster("local[*]").setAppName("createDF")
val spark = SparkSession.builder().config(conf).getOrCreate()
val source = spark.read.format("csv").option("header","true").load("in/score.csv")
source.printSchema()
source.show(5)
/*
root
|-- sid: string (nullable = true)
|-- cid: string (nullable = true)
|-- score: string (nullable = true)
+---+---+-----+
|sid|cid|score|
+---+---+-----+
| 01| 01| 80|
| 01| 02| 90|
| 01| 03| 99|
| 02| 01| 70|
| 02| 02| 60|
+---+---+-----+
only showing top 5 rows
*/
要求以cid为分组条件,求出每个分组内score最高的三条记录
RDD
// 转为RDD(也可以直接用SparkContext去读取)
source.rdd.map(e => (e.getAs[String]("sid"), e.getAs[String]("cid"), e.getAs[String]("score")))
// 数据转换为(cid,(sid,score))形式
.map(e => (e._2.toInt, (e._1.toInt, e._3.toInt)))
// 根据cid分组
.groupByKey()
// 对分组后的value进行转换,只保留前三名
.mapValues(iter => iter.toArray.sortBy(_._2).takeRight(3))
// 重新分为多行
.flatMap(e => e._2.map(m => (e._1, m._1, m._2)))
.collect().foreach(println)
/*
(1,5,76)
(1,1,80)
(1,3,80)
(3,3,80)
(3,7,98)
(3,1,99)
(2,5,87)
(2,7,89)
(2,1,90)
*/
DataFrame
source.select('cid, 'sid, 'score,
row_number().over(Window.partitionBy('cid).orderBy('score.desc)).as("rank"))
.where('rank <= lit(3))
.show()
/*
+---+---+-----+----+
|cid|sid|score|rank|
+---+---+-----+----+
| 01| 01| 80| 1|
| 01| 03| 80| 2|
| 01| 05| 76| 3|
| 03| 01| 99| 1|
| 03| 07| 98| 2|
| 03| 02| 80| 3|
| 02| 01| 90| 1|
| 02| 07| 89| 2|
| 02| 05| 87| 3|
+---+---+-----+----+
*/
SQL语句
source.createOrReplaceTempView("score")
spark.sql(
"""
|select
| sid,
| cid,
| score,
| rank
|from
| (select
| sid,
| cid,
| score,
| row_number() over(partition by cid order by score desc) as rank
| from
| score) as tab
|where
| rank <= 3
""".stripMargin)
.show()
/*
+---+---+-----+----+
|sid|cid|score|rank|
+---+---+-----+----+
| 01| 01| 80| 1|
| 03| 01| 80| 2|
| 05| 01| 76| 3|
| 01| 03| 99| 1|
| 07| 03| 98| 2|
| 02| 03| 80| 3|
| 01| 02| 90| 1|
| 07| 02| 89| 2|
| 05| 02| 87| 3|
+---+---+-----+----+
*/
保存数据
DataFrame/DataSet对象可以调用write
函数获得一个DataFrameWriter
对象,类似于DataFrameReader
,该对象同样可以连接很多下游写入对象,函数调用方法也与DataFrameReader
基本类似。下面以写入hive为例做简单演示:
object CreateDF {
def main(args: Array[String]): Unit = {
System.setProperty("HADOOP_USER_NAME", "root")
val conf = new SparkConf().setMaster("local[*]").setAppName("createDF")
val spark = SparkSession.builder()
.config(conf)
.enableHiveSupport() // 设置支持Hive
.config("hive.metastore.uris", "thrift://192.168.226.10:9083") // 设置HiveIP和端口
.getOrCreate()
val dataFrame = spark.read.format("csv").option("header", "true").load("file:///D:\\file\\project\\spark\\after_class\\in\\event_attendees.csv")
dataFrame.write.mode(SaveMode.Append).saveAsTable("today.event_attendees")
spark.close()
}
}
运行后,可以看到程序读取了event_attendees.csv文件中的内容,并将其写入了hive表
注意事项:
- 如果Hive部署在高可用HDFS上,可能会因为无法识别集群名称导致异常,可以像配置HiveIP和端口这样,将HDFS集群名和对应的各个结点通信IP和端口配置上,或者采用更简便的方法,将Hadoop配置文件中的
core-site.xml
和hdfs-site.xml
放在resources
文件夹下。IDEA运行时会采用其中的配置 - 如果报出权限不足异常,且用户名为本机用户名,可以配置上以上代码第一行的系统设置,切换用户
- 存入的目标服务要确认开启,对本例的存入Hive而言,需要开启metastore服务:
nohup hive --service metastore &
到这里,就基本完成了使用Spark读取数据、处理数据和写入结果的一套业务操作