3、使用IDEA开发Spark SQL
3.1 创建DataFrame/DataSet
Spark会根据文件信息尝试着去推断DataFrame/DataSet的Schema,当然我们也可以手动指定,手动指定的方式有以下几种:
第1种:指定列名添加Schema 第2种:通过StructType指定Schema 第3种:编写样例类,利用反射机制推断Schema
3.1.1 指定列名添加Schema
package cn.itcast.sql import org.apache.spark.SparkContext import org.apache.spark.rdd.RDD import org.apache.spark.sql.{DataFrame, SparkSession} object CreateDFDS { def main(args: Array[String]): Unit = { //1.创建SparkSession val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL").getOrCreate() val sc: SparkContext = spark.sparkContext sc.setLogLevel("WARN") //2.读取文件 val fileRDD: RDD[String] = sc.textFile("D:\\data\\person.txt") val linesRDD: RDD[Array[String]] = fileRDD.map(_.split(" ")) val rowRDD: RDD[(Int, String, Int)] = linesRDD.map(line =>(line(0).toInt,line(1),line(2).toInt)) //3.将RDD转成DF //注意:RDD中原本没有toDF方法,新版本中要给它增加一个方法,可以使用隐式转换 import spark.implicits._ val personDF: DataFrame = rowRDD.toDF("id","name","age") personDF.show(10) personDF.printSchema() sc.stop() spark.stop() } }
3.1.2 StructType指定Schema-了解
package cn.itcast.sql import org.apache.spark.SparkContext import org.apache.spark.rdd.RDD import org.apache.spark.sql.types._ import org.apache.spark.sql.{DataFrame, Row, SparkSession} object CreateDFDS2 { def main(args: Array[String]): Unit = { //1.创建SparkSession val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL").getOrCreate() val sc: SparkContext = spark.sparkContext sc.setLogLevel("WARN") //2.读取文件 val fileRDD: RDD[String] = sc.textFile("D:\\data\\person.txt") val linesRDD: RDD[Array[String]] = fileRDD.map(_.split(" ")) val rowRDD: RDD[Row] = linesRDD.map(line =>Row(line(0).toInt,line(1),line(2).toInt)) //3.将RDD转成DF //注意:RDD中原本没有toDF方法,新版本中要给它增加一个方法,可以使用隐式转换 //import spark.implicits._ val schema: StructType = StructType(Seq( StructField("id", IntegerType, true),//允许为空 StructField("name", StringType, true), StructField("age", IntegerType, true)) ) val personDF: DataFrame = spark.createDataFrame(rowRDD,schema) personDF.show(10) personDF.printSchema() sc.stop() spark.stop() } }
3.1.3 反射推断Schema–掌握
package cn.itcast.sql import org.apache.spark.SparkContext import org.apache.spark.rdd.RDD import org.apache.spark.sql.{DataFrame, SparkSession} object CreateDFDS3 { case class Person(id:Int,name:String,age:Int) def main(args: Array[String]): Unit = { //1.创建SparkSession val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL") .getOrCreate() val sc: SparkContext = spark.sparkContext sc.setLogLevel("WARN") //2.读取文件 val fileRDD: RDD[String] = sc.textFile("D:\\data\\person.txt") val linesRDD: RDD[Array[String]] = fileRDD.map(_.split(" ")) val rowRDD: RDD[Person] = linesRDD.map(line =>Person(line(0).toInt,line(1),line(2).toInt)) //3.将RDD转成DF //注意:RDD中原本没有toDF方法,新版本中要给它增加一个方法,可以使用隐式转换 import spark.implicits._ //注意:上面的rowRDD的泛型是Person,里面包含了Schema信息 //所以SparkSQL可以通过反射自动获取到并添加给DF val personDF: DataFrame = rowRDD.toDF personDF.show(10) personDF.printSchema() sc.stop() spark.stop() } }
3.2 花式查询
package cn.itcast.sql import org.apache.spark.SparkContext import org.apache.spark.rdd.RDD import org.apache.spark.sql.{DataFrame, SparkSession} object QueryDemo { case class Person(id:Int,name:String,age:Int) def main(args: Array[String]): Unit = { //1.创建SparkSession val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL") .getOrCreate() val sc: SparkContext = spark.sparkContext sc.setLogLevel("WARN") //2.读取文件 val fileRDD: RDD[String] = sc.textFile("D:\\data\\person.txt") val linesRDD: RDD[Array[String]] = fileRDD.map(_.split(" ")) val rowRDD: RDD[Person] = linesRDD.map(line =>Person(line(0).toInt,line(1),line(2).toInt)) //3.将RDD转成DF //注意:RDD中原本没有toDF方法,新版本中要给它增加一个方法,可以使用隐式转换 import spark.implicits._ //注意:上面的rowRDD的泛型是Person,里面包含了Schema信息 //所以SparkSQL可以通过反射自动获取到并添加给DF val personDF: DataFrame = rowRDD.toDF personDF.show(10) personDF.printSchema() //=======================SQL方式查询======================= //0.注册表 personDF.createOrReplaceTempView("t_person") //1.查询所有数据 spark.sql("select * from t_person").show() //2.查询age+1 spark.sql("select age,age+1 from t_person").show() //3.查询age最大的两人 spark.sql("select name,age from t_person order by age desc limit 2").show() //4.查询各个年龄的人数 spark.sql("select age,count(*) from t_person group by age").show() //5.查询年龄大于30的 spark.sql("select * from t_person where age > 30").show() //=======================DSL方式查询======================= //1.查询所有数据 personDF.select("name","age") //2.查询age+1 personDF.select($"name",$"age" + 1) //3.查询age最大的两人 personDF.sort($"age".desc).show(2) //4.查询各个年龄的人数 personDF.groupBy("age").count().show() //5.查询年龄大于30的 personDF.filter($"age" > 30).show() sc.stop() spark.stop() } }
3.3 相互转化
RDD、DF、DS之间的相互转换有很多(6种),但是我们实际操作就只有2类: 1)使用RDD算子操作 2)使用DSL/SQL对表操作
package cn.itcast.sql import org.apache.spark.SparkContext import org.apache.spark.rdd.RDD import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession} object TransformDemo { case class Person(id:Int,name:String,age:Int) def main(args: Array[String]): Unit = { //1.创建SparkSession val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL").getOrCreate() val sc: SparkContext = spark.sparkContext sc.setLogLevel("WARN") //2.读取文件 val fileRDD: RDD[String] = sc.textFile("D:\\data\\person.txt") val linesRDD: RDD[Array[String]] = fileRDD.map(_.split(" ")) val personRDD: RDD[Person] = linesRDD.map(line =>Person(line(0).toInt,line(1),line(2).toInt)) //3.将RDD转成DF //注意:RDD中原本没有toDF方法,新版本中要给它增加一个方法,可以使用隐式转换 import spark.implicits._ //注意:上面的rowRDD的泛型是Person,里面包含了Schema信息 //所以SparkSQL可以通过反射自动获取到并添加给DF //=========================相互转换====================== //1.RDD-->DF val personDF: DataFrame = personRDD.toDF //2.DF-->RDD val rdd: RDD[Row] = personDF.rdd //3.RDD-->DS val DS: Dataset[Person] = personRDD.toDS() //4.DS-->RDD val rdd2: RDD[Person] = DS.rdd //5.DF-->DS val DS2: Dataset[Person] = personDF.as[Person] //6.DS-->DF val DF: DataFrame = DS2.toDF() sc.stop() spark.stop() } }
3.4 Spark SQL完成WordCount
3.4.1 SQL风格
package cn.itcast.sql import org.apache.spark.SparkContext import org.apache.spark.sql.{DataFrame, Dataset, SparkSession} object WordCount { def main(args: Array[String]): Unit = { //1.创建SparkSession val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL").getOrCreate() val sc: SparkContext = spark.sparkContext sc.setLogLevel("WARN") //2.读取文件 val fileDF: DataFrame = spark.read.text("D:\\data\\words.txt") val fileDS: Dataset[String] = spark.read.textFile("D:\\data\\words.txt") //fileDF.show() //fileDS.show() //3.对每一行按照空格进行切分并压平 //fileDF.flatMap(_.split(" ")) //注意:错误,因为DF没有泛型,不知道_是String import spark.implicits._ val wordDS: Dataset[String] = fileDS.flatMap(_.split(" "))//注意:正确,因为DS有泛型,知道_是String //wordDS.show() /* +-----+ |value| +-----+ |hello| | me| |hello| | you| ... */ //4.对上面的数据进行WordCount wordDS.createOrReplaceTempView("t_word") val sql = """ |select value ,count(value) as count |from t_word |group by value |order by count desc """.stripMargin spark.sql(sql).show() sc.stop() spark.stop() } }
3.4.2 DSL风格
package cn.itcast.sql import org.apache.spark.SparkContext import org.apache.spark.sql.{DataFrame, Dataset, SparkSession} object WordCount2 { def main(args: Array[String]): Unit = { //1.创建SparkSession val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL").getOrCreate() val sc: SparkContext = spark.sparkContext sc.setLogLevel("WARN") //2.读取文件 val fileDF: DataFrame = spark.read.text("D:\\data\\words.txt") val fileDS: Dataset[String] = spark.read.textFile("D:\\data\\words.txt") //fileDF.show() //fileDS.show() //3.对每一行按照空格进行切分并压平 //fileDF.flatMap(_.split(" ")) //注意:错误,因为DF没有泛型,不知道_是String import spark.implicits._ val wordDS: Dataset[String] = fileDS.flatMap(_.split(" "))//注意:正确,因为DS有泛型,知道_是String //wordDS.show() /* +-----+ |value| +-----+ |hello| | me| |hello| | you| ... */ //4.对上面的数据进行WordCount wordDS.groupBy("value").count().orderBy($"count".desc).show() sc.stop() spark.stop() } }
4、Spark SQL多数据源交互
Spark SQL可以与多种数据源交互,如普通文本、json、parquet、csv、MySQL等 1.写入不同数据源 2.读取不同数据源
4.1 写数据
package cn.itcast.sql import java.util.Properties import org.apache.spark.SparkContext import org.apache.spark.rdd.RDD import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession} object WriterDataSourceDemo { case class Person(id:Int,name:String,age:Int) def main(args: Array[String]): Unit = { //1.创建SparkSession val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL") .getOrCreate() val sc: SparkContext = spark.sparkContext sc.setLogLevel("WARN") //2.读取文件 val fileRDD: RDD[String] = sc.textFile("D:\\data\\person.txt") val linesRDD: RDD[Array[String]] = fileRDD.map(_.split(" ")) val rowRDD: RDD[Person] = linesRDD.map(line =>Person(line(0).toInt,line(1),line(2).toInt)) //3.将RDD转成DF //注意:RDD中原本没有toDF方法,新版本中要给它增加一个方法,可以使用隐式转换 import spark.implicits._ //注意:上面的rowRDD的泛型是Person,里面包含了Schema信息 //所以SparkSQL可以通过反射自动获取到并添加给DF val personDF: DataFrame = rowRDD.toDF //==================将DF写入到不同数据源=================== //Text data source supports only a single column, and you have 3 columns.; //personDF.write.text("D:\\data\\output\\text") personDF.write.json("D:\\data\\output\\json") personDF.write.csv("D:\\data\\output\\csv") personDF.write.parquet("D:\\data\\output\\parquet") val prop = new Properties() prop.setProperty("user","root") prop.setProperty("password","root") personDF.write.mode(SaveMode.Overwrite).jdbc( "jdbc:mysql://localhost:3306/bigdata?characterEncoding=UTF-8","person",prop) println("写入成功") sc.stop() spark.stop() } }
4.2 读数据
package cn.itcast.sql import java.util.Properties import org.apache.spark.SparkContext import org.apache.spark.sql.SparkSession object ReadDataSourceDemo { def main(args: Array[String]): Unit = { //1.创建SparkSession val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL") .getOrCreate() val sc: SparkContext = spark.sparkContext sc.setLogLevel("WARN") //2.读取文件 spark.read.json("D:\\data\\output\\json").show() spark.read.csv("D:\\data\\output\\csv").toDF("id","name","age").show() spark.read.parquet("D:\\data\\output\\parquet").show() val prop = new Properties() prop.setProperty("user","root") prop.setProperty("password","root") spark.read.jdbc( "jdbc:mysql://localhost:3306/bigdata?characterEncoding=UTF-8","person",prop).show() sc.stop() spark.stop() } }
4.3 总结
1.SparkSQL写数据: DataFrame/DataSet.write.json/csv/jdbc 2.SparkSQL读数据: SparkSession.read.json/csv/text/jdbc/format
5、Spark SQL自定义函数
5.1 自定义函数分类
类似于hive当中的自定义函数, spark同样可以使用自定义函数来实现新的功能。
spark中的自定义函数有如下3类
1.UDF(User-Defined-Function) 输入一行,输出一行 2.UDAF(User-Defined Aggregation Funcation) 输入多行,输出一行 3.UDTF(User-Defined Table-Generating Functions) 输入一行,输出多行
5.2 自定义UDF
需求
有udf.txt数据格式如下: Hello abc study small
通过自定义UDF函数将每一行数据转换成大写
select value,smallToBig(value) from t_word
代码演示
package cn.itcast.sql import org.apache.spark.SparkContext import org.apache.spark.sql.{Dataset, SparkSession} object UDFDemo { def main(args: Array[String]): Unit = { //1.创建SparkSession val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL").getOrCreate() val sc: SparkContext = spark.sparkContext sc.setLogLevel("WARN") //2.读取文件 val fileDS: Dataset[String] = spark.read.textFile("D:\\data\\udf.txt") fileDS.show() /* +----------+ | value| +----------+ |helloworld| | abc| | study| | smallWORD| +----------+ */ /* 将每一行数据转换成大写 select value,smallToBig(value) from t_word */ //注册一个函数名称为smallToBig,功能是传入一个String,返回一个大写的String spark.udf.register("smallToBig",(str:String) => str.toUpperCase()) fileDS.createOrReplaceTempView("t_word") //使用我们自己定义的函数 spark.sql("select value,smallToBig(value) from t_word").show() /* +----------+---------------------+ | value|UDF:smallToBig(value)| +----------+---------------------+ |helloworld| HELLOWORLD| | abc| ABC| | study| STUDY| | smallWORD| SMALLWORD| +----------+---------------------+ */ sc.stop() spark.stop() } }
5.3 自定义UDAF[了解]
需求 有udaf.json数据内容如下 {"name":"Michael","salary":3000} {"name":"Andy","salary":4500} {"name":"Justin","salary":3500} {"name":"Berta","salary":4000} 求取平均工资
继承UserDefinedAggregateFunction方法重写说明
inputSchema:输入数据的类型 bufferSchema:产生中间结果的数据类型 dataType:最终返回的结果类型 deterministic:确保一致性,一般用true initialize:指定初始值 update:每有一条数据参与运算就更新一下中间结果(update相当于在每一个分区中的运算) merge:全局聚合(将每个分区的结果进行聚合) evaluate:计算最终的结果
代码演示
package cn.itcast.sql import org.apache.spark.SparkContext importorg.apache.spark.sql.expressions{MutableAggregationBuffer,UserDefinedAggregateFunction} import org.apache.spark.sql.types._ import org.apache.spark.sql.{DataFrame, Row, SparkSession} object UDAFDemo { def main(args: Array[String]): Unit = { //1.获取sparkSession val spark: SparkSession = SparkSession.builder().appName("SparkSQL").master("local[*]").getOrCreate() val sc: SparkContext = spark.sparkContext sc.setLogLevel("WARN") //2.读取文件 val employeeDF: DataFrame = spark.read.json("D:\\data\\udaf.json") //3.创建临时表 employeeDF.createOrReplaceTempView("t_employee") //4.注册UDAF函数 spark.udf.register("myavg",new MyUDAF) //5.使用自定义UDAF函数 spark.sql("select myavg(salary) from t_employee").show() //6.使用内置的avg函数 spark.sql("select avg(salary) from t_employee").show() } } class MyUDAF extends UserDefinedAggregateFunction{ //输入的数据类型的schema override def inputSchema: StructType = { StructType(StructField("input",LongType)::Nil) } //缓冲区数据类型schema,就是转换之后的数据的schema override def bufferSchema: StructType = { StructType(StructField("sum",LongType)::StructField("total",LongType)::Nil) } //返回值的数据类型 override def dataType: DataType = { DoubleType } //确定是否相同的输入会有相同的输出 override def deterministic: Boolean = { true } //初始化内部数据结构 override def initialize(buffer: MutableAggregationBuffer): Unit = { buffer(0) = 0L buffer(1) = 0L } //更新数据内部结构,区内计算 override def update(buffer: MutableAggregationBuffer, input: Row): Unit = { //所有的金额相加 buffer(0) = buffer.getLong(0) + input.getLong(0) //一共有多少条数据 buffer(1) = buffer.getLong(1) + 1 } //来自不同分区的数据进行合并,全局合并 override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = { buffer1(0) =buffer1.getLong(0) + buffer2.getLong(0) buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1) } //计算输出数据值 override def evaluate(buffer: Row): Any = { buffer.getLong(0).toDouble / buffer.getLong(1) } }
6、扩展:开窗函数
6.1 概述
https://www.cnblogs.com/qiuting/p/7880500.html
介绍
开窗函数的引入是为了既显示聚集前的数据,又显示聚集后的数据。即在每一行的最后一列添加聚合函数的结果。 开窗用于为行定义一个窗口(这里的窗口是指运算将要操作的行的集合),它对一组值进行操作,不需要使用 GROUP BY 子句对数据进行分组,能够在同一行中同时返回基础行的列和聚合列。
聚合函数和开窗函数
聚合函数是将多行变成一行,count,avg… 开窗函数是将一行变成多行; 聚合函数如果要显示其他的列必须将列加入到group by中 开窗函数可以不使用group by,直接将所有信息显示出来
开窗函数分类
1.聚合开窗函数 聚合函数(列) OVER(选项),这里的选项可以是PARTITION BY 子句,但不可以是 ORDER BY 子句。 2.排序开窗函数 排序函数(列) OVER(选项),这里的选项可以是ORDER BY 子句,也可以是 OVER(PARTITION BY 子句 ORDER BY 子句),但不可以是 PARTITION BY 子句。
6.2 准备工作
/export/servers/spark/bin/spark-shell --master spark://node01:7077,node02:7077 case class Score(name: String, clazz: Int, score: Int) val scoreDF = spark.sparkContext.makeRDD(Array( Score("a1", 1, 80), Score("a2", 1, 78), Score("a3", 1, 95), Score("a4", 2, 74), Score("a5", 2, 92), Score("a6", 3, 99), Score("a7", 3, 99), Score("a8", 3, 45), Score("a9", 3, 55), Score("a10", 3, 78), Score("a11", 3, 100)) ).toDF("name", "class", "score") scoreDF.createOrReplaceTempView("scores") scoreDF.show() +----+-----+-----+ |name|class|score| +----+-----+-----+ | a1| 1| 80| | a2| 1| 78| | a3| 1| 95| | a4| 2| 74| | a5| 2| 92| | a6| 3| 99| | a7| 3| 99| | a8| 3| 45| | a9| 3| 55| | a10| 3| 78| | a11| 3| 100| +----+-----+-----+
6.3 聚合开窗函数
示例1
OVER 关键字表示把聚合函数当成聚合开窗函数而不是聚合函数。SQL标准允许将所有聚合函数用做聚合开窗函数。
spark.sql("select count(name) from scores").show spark.sql("select name, class, score, count(name) over() name_count from scores").show 查询结果如下所示: +----+-----+-----+----------+ |name|class|score|name_count| +----+-----+-----+----------+ | a1| 1| 80| 11| | a2| 1| 78| 11| | a3| 1| 95| 11| | a4| 2| 74| 11| | a5| 2| 92| 11| | a6| 3| 99| 11| | a7| 3| 99| 11| | a8| 3| 45| 11| | a9| 3| 55| 11| | a10| 3| 78| 11| | a11| 3| 100| 11| +----+-----+-----+----------+
示例2
OVER 关键字后的括号中还可以添加选项用以改变进行聚合运算的窗口范围。 如果 OVER 关键字后的括号中的选项为空,则开窗函数会对结果集中的所有行进行聚合运算。 开窗函数的 OVER 关键字后括号中的可以使用 PARTITION BY 子句来定义行的分区来供进行聚合计算。与 GROUP BY 子句不同,PARTITION BY 子句创建的分区是独立于结果集的,创建的分区只是供进行聚合计算的,而且不同的开窗函数所创建的分区也不互相影响。 下面的 SQL 语句用于显示按照班级分组后每组的人数: OVER(PARTITION BY class)表示对结果集按照 class 进行分区,并且计算当前行所属的组的聚合计算结果。 spark.sql(“select name, class, score, count(name) over(partition by class) name_count from scores”).show
查询结果如下所示: +----+-----+-----+----------+ |name|class|score|name_count| +----+-----+-----+----------+ | a1| 1| 80| 3| | a2| 1| 78| 3| | a3| 1| 95| 3| | a6| 3| 99| 6| | a7| 3| 99| 6| | a8| 3| 45| 6| | a9| 3| 55| 6| | a10| 3| 78| 6| | a11| 3| 100| 6| | a4| 2| 74| 2| | a5| 2| 92| 2| +----+-----+-----+----------+