一、Spark SQL
- Spark SQL是Spark的一个模块,主要用于进行结构化数据的处理,它提供的最核心的编程抽象,就是
DataFrame DataFrame=RDD+Schema,它其实和关系型数据库中的表非常类似,DataFrame可以通过很多来源进行构建- Spark1.3出现DataFrame,Spark1.6出现DataSet,在Spark2.0中两者统一,DataFrame等于DataSet[Row]
1.1、SparkSession
- 要使用Spark SQL,首先需要创建一个SparkSession对象
- SparkSession中包含SparkContext和SqlContext
- 使用SparkSession,可以从RDD,Hive表或者其他数据源创建DataFrame
代码实现
-
添加pom依赖
<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-sql_2.12</artifactId> <version>3.1.2</version> </dependency> -
scala代码
package com.strivelearn.scala.sql import org.apache.spark.SparkConf import org.apache.spark.sql.SparkSession /** * @author strivelearn * @version SqlDemoScala.java, 2022年11月29日 */ object SqlDemoScala { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local") //创建sparkSession对象 val sparkSession = SparkSession.builder().appName("SqlDemoScala").config(conf).getOrCreate() //读取json文件,获取dataFrame val dataFrame = sparkSession.read.json("/Users/strivelearn/Desktop/req.json") //查看DataFrame中的数据 dataFrame.show() sparkSession.stop() } } -
java代码
package com.strivelearn.java.sql; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; /** * @author strivelearn * @version SqlDemoJava.java, 2022年11月29日 */ public class SqlDemoJava { public static void main(String[] args) { //1.创建sparkContext SparkConf sparkConf = new SparkConf(); sparkConf.setMaster("local"); SparkSession sparkSession = SparkSession.builder().appName("SqlDemoJava").config(sparkConf).getOrCreate(); Dataset<Row> json = sparkSession.read().json("/Users/strivelearn/Desktop/req1.json").cache(); json.show(); sparkSession.stop(); } } -
req.json文件内容
注意:这个json文件需要是压缩后的json文件,不能是格式化的
{"name":"zhangsan","age":18} {"name":"lisi","age":21} {"name":"zzz","age":17} -
代码演示效果
二、DataFrame常见算子操作
- printSchema
- show
- select
- filter\where
- groupBy
- count
package com.strivelearn.java.sql;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import static org.apache.spark.sql.functions.col;
/**
* @author strivelearn
* @version SqlDemoJava.java, 2022年11月29日
*/
public class SqlDemoJava {
public static void main(String[] args) {
//1.创建sparkContext
SparkConf sparkConf = new SparkConf();
sparkConf.setMaster("local");
SparkSession sparkSession = SparkSession.builder().appName("SqlDemoJava").config(sparkConf).getOrCreate();
Dataset<Row> dataset = sparkSession.read().json("/Users/strivelearn/Desktop/req1.json").cache();
//打印schema数据
dataset.printSchema();
//默认显示所有数据,可以通过参数控制多少条
dataset.show();
//查询中的指定字段信息
dataset.select("name").show();
//使用select 的时候可以对数据做一些操作,需要引入import static org.apache.spark.sql.functions.col;
dataset.select(col("name").as("aaa"), col("age").plus(1)).show();
//对数据进行过滤
dataset.filter(col("age").gt(20)).show();
//查询中的指定字段信息
dataset.where(col("age").lt(18)).show();
//对数据进行分组求和
dataset.groupBy(col("age")).count().show();
sparkSession.stop();
}
}
三、DataFrame的sql操作
如何使用sql语句查询DataFrame中的数据
- 先将DataFrame注册为一个临时表
- 使用sparkSession中的sql函数执行sql语句
package com.strivelearn.java.sql;
import org.apache.spark.SparkConf;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
/**
* @author strivelearn
* @version DataFrameSqlJava.java, 2022年11月29日
*/
public class DataFrameSqlJava {
public static void main(String[] args) {
//1.创建sparkContext
SparkConf sparkConf = new SparkConf();
sparkConf.setMaster("local");
SparkSession sparkSession = SparkSession.builder().appName("DataFrameSqlJava").config(sparkConf).getOrCreate();
Dataset<Row> dataset = sparkSession.read().json("/Users/strivelearn/Desktop/req1.json").cache();
//将DataFrame注册为一个临时表
dataset.createOrReplaceTempView("student");
//使用sql查询临时表的数据
sparkSession.sql("select name from student").show();
}
}
四、RDD转换为DataFrame
Q:为什么要将RDD转换为DataFrame?
在实际工作中,我们可能会把hdfs上的一些日志数据加载进来,然后进行处理,比如清洗操作,最终变成结构化数据。我们希望对这些数据进行一些统计。
Saprk SQL支持两种方式将RDD转换为DataFrmae
- 反射方式
- 编程方式
4.1、反射方式
Scala具有隐式转换的特性,所以Spark SQL的Scala接口是支持自动将包含了case class的RDD转换为DataFrame
package com.strivelearn.scala.sql
import org.apache.spark.sql.SparkSession
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author strivelearn
* @version RddToDataFrameByReflectScala.java, 2022年11月29日
*/
object RddToDataFrameByReflectScala {
def main(args: Array[String]): Unit = {
//创建SparkContext
val conf = new SparkConf().setMaster("local")
val sparkSession = SparkSession.builder().appName("RddToDataFrameByReflectScala").config(conf).getOrCreate()
val context = sparkSession.sparkContext
val dataRdd = context.parallelize(Array(("jack", 19), ("lisi", 20)))
//基于反射直接将包含Student对象的dataRdd转换为dataFrame
//需要导入隐式转换
import sparkSession.implicits._
val frame = dataRdd.map(tup => Student(tup._1, tup._2)).toDF()
frame.createOrReplaceTempView("student")
sparkSession.sql("select age from student").show()
sparkSession.stop()
}
}
case class Student(name: String, age: Int)
package com.strivelearn.java.sql;
import java.util.Arrays;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import scala.Tuple2;
/**
* @author strivelearn
* @version RddToDataFrameByReflectJava.java, 2022年11月29日
*/
public class RddToDataFrameByReflectJava {
public static void main(String[] args) {
//1.创建sparkContext
SparkConf sparkConf = new SparkConf();
sparkConf.setMaster("local");
SparkSession sparkSession = SparkSession.builder().appName("RddToDataFrameByReflectJava").config(sparkConf).getOrCreate();
//获取SparkContext
//从SparkSession中获取的是scala中的SparkContext,所以需要转换成Java中的SparkContext
JavaSparkContext javaSparkContext = JavaSparkContext.fromSparkContext(sparkSession.sparkContext());
Tuple2<String, Integer> lisi = new Tuple2<>("lisi", 18);
Tuple2<String, Integer> zhangsan = new Tuple2<>("zhangsan", 12);
Tuple2<String, Integer> hanming = new Tuple2<>("hanming", 18);
JavaRDD<Tuple2<String, Integer>> dataRdd = javaSparkContext.parallelize(Arrays.asList(lisi, zhangsan, hanming));
JavaRDD<Student> studRdd = dataRdd.map(tuple -> new Student(tuple._1, tuple._2));
//注意:Student这个类必须声明为public,并且必须实现序列化
Dataset<Row> dataFrame = sparkSession.createDataFrame(studRdd, Student.class);
dataFrame.createOrReplaceTempView("student");
sparkSession.sql("select name from student where age>12").show();
sparkSession.stop();
}
}
4.2、编程方式
使用编程方式指定元数据
如果在编写程序的时候,我们还不知道RDD的元数据,只有在程序运行的时候才能动态得知元数据,那么只能通过这种动态构建元数据的方式。就无法使用反射的方式。也就是当case class中的字段无法预先定义的时候,就只能用编程的方式动态指定元数据了。
package com.strivelearn.scala.sql
import org.apache.spark.SparkConf
import org.apache.spark.sql.types.{IntegerType, StringType, StructField, StructType}
import org.apache.spark.sql.{Row, SparkSession}
/**
* 使用编程方式实现RDD转换为DataFrame
*
* @author strivelearn
* @version RddToDataFrameByProgramScala.java, 2022年12月03日
*/
object RddToDataFrameByProgramScala {
def main(args: Array[String]): Unit = {
//创建SparkContext
val conf = new SparkConf().setMaster("local")
val sparkSession = SparkSession.builder().appName("RddToDataFrameByProgramScala").config(conf).getOrCreate()
val context = sparkSession.sparkContext
val dataRdd = context.parallelize(Array(("jack", 19), ("lisi", 20)))
//假设上面的数据类型,我们不知道,没有创建对象,此时,我们组装rowRDD
val rowRdd = dataRdd.map(tuple => Row(tuple._1, tuple._2))
//指定元数据信息。这个元数据信息就可以动态从外部获取了,比较灵活
val schema = StructType(Array(
StructField("name", StringType, true),
StructField("age", IntegerType, true)
))
//组装DataFrame
val studentDf = sparkSession.createDataFrame(rowRdd, schema)
//下面就可以通过DataFrame的方式操作dataRdd中的数据了
studentDf.createOrReplaceTempView("student")
//执行sql查询
val resDf = sparkSession.sql("select name, age from student")
//将DataFrame转化为Rdd
val resRdd = resDf.rdd
resRdd.map(row => (row(0).toString, row(1).toString.toInt)).collect().foreach(println)
sparkSession.stop()
}
}
package com.strivelearn.java.sql;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.types.DataTypes;
import org.apache.spark.sql.types.StructField;
import org.apache.spark.sql.types.StructType;
import scala.Tuple2;
/**
* @author strivelearn
* @version RddToDataFrameByProgramJava.java, 2022年12月03日
*/
public class RddToDataFrameByProgramJava {
public static void main(String[] args) {
//1.创建sparkContext
SparkConf sparkConf = new SparkConf();
sparkConf.setMaster("local");
SparkSession sparkSession = SparkSession.builder().appName("RddToDataFrameByProgramJava").config(sparkConf).getOrCreate();
//获取SparkContext
//从SparkSession中获取的是scala中的SparkContext,所以需要转换成Java中的SparkContext
JavaSparkContext javaSparkContext = JavaSparkContext.fromSparkContext(sparkSession.sparkContext());
Tuple2<String, Integer> lisi = new Tuple2<>("lisi", 18);
Tuple2<String, Integer> zhangsan = new Tuple2<>("zhangsan", 12);
Tuple2<String, Integer> hanming = new Tuple2<>("hanming", 18);
JavaRDD<Tuple2<String, Integer>> dataRdd = javaSparkContext.parallelize(Arrays.asList(lisi, zhangsan, hanming));
//组装RDD
JavaRDD<Row> rowJavaRDD = dataRdd.map(tup -> RowFactory.create(tup._1, tup._2));
//指定元数据信息
ArrayList<StructField> structFieldList = new ArrayList<>();
structFieldList.add(DataTypes.createStructField("name", DataTypes.StringType, true));
structFieldList.add(DataTypes.createStructField("age", DataTypes.IntegerType, true));
StructType schema = DataTypes.createStructType(structFieldList);
//构建DataFrame
Dataset<Row> studentDf = sparkSession.createDataFrame(rowJavaRDD, schema);
studentDf.createOrReplaceTempView("student");
Dataset<Row> resDf = sparkSession.sql("select name, age from student");
JavaRDD<Row> resRdd = resDf.javaRDD();
resRdd.map(row -> new Tuple2(row.getString(0), row.getInt(1))).collect().forEach(System.out::println);
sparkSession.stop();
}
}
五、load和save操作
- load操作主要用于加载数据,创建DataFrame
- save操作主要用于将DataFrame中的数据保存到文件中
- 如果不指定format,则默认操作的数据时parquet格式的
package com.strivelearn.scala.sql
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
/**
* @author strivelearn
* @version LoadAndSaveOpScala.java, 2022年12月03日
*/
object LoadAndSaveOpScala {
def main(args: Array[String]): Unit = {
//创建SparkContext
val conf = new SparkConf().setMaster("local")
val sparkSession = SparkSession.builder().appName("LoadAndSaveOpScala").config(conf).getOrCreate()
//读取数据
val studentDf = sparkSession.read.format("json").load("xxx/student.json")
//保存数据
studentDf.select("name", "age").write.format("csv").save("hdfs://localhost:xxx")
sparkSession.stop()
}
}
package com.strivelearn.java.sql;
import org.apache.spark.SparkConf;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
/**
* @author strivelearn
* @version LoadAndSaveOpJava.java, 2022年12月03日
*/
public class LoadAndSaveOpJava {
public static void main(String[] args) {
//1.创建sparkContext
SparkConf sparkConf = new SparkConf();
sparkConf.setMaster("local");
SparkSession sparkSession = SparkSession.builder().appName("LoadAndSaveOpJava").config(sparkConf).getOrCreate();
Dataset<Row> stuDf = sparkSession.read().format("json").load("student.json");
stuDf.select("name", "age").write().format("csv").save("hdfs://localhost");
sparkSession.stop();
}
}
六、SaveMode
-
SaveMode.ErrorIfExists(默认)
如果目标位置已经存在数据,那么抛出一个异常
-
SaveMode.Append
如果目标位置已经存在数据,那么将数据追加进去
-
SaveMode.Overwrite
如果目标位置已经存在数据,那么就将已经存在的数据删除,用新数据进行覆盖
-
SaveMode.Ignore
如果目标位置已经存在数据,那么就忽略,不做任何操作
stuDf.select("name", "age").write().format("csv").mode(SaveMode.Append).save("hdfs://localhost");
七、Spark内置函数
| 种类 | 函数 |
|---|---|
| 聚合函数 | avg,count,countDistinct,first,last,max,mean,min,sum,sumDistinct |
| 集合函数 | array_contains,explode,size |
| 日期、时间函数 | datediff,date_add,date_sub,add_months,last_day,next_day, months_between,current_date,current_timestamp,date_format |
| 数学函数 | abs,ceil,floor,round |
| 混合函数 | if,isnull,md5,not,rand,when |
| 字符串函数 | concat,get_json_object,length,reverse,split,upper |
| 窗口函数 | denseRank,rank,rowNumber |