大数据开发快速上手SparkSQL(第三十六篇)

143 阅读6分钟

一、Spark SQL

  1. Spark SQL是Spark的一个模块,主要用于进行结构化数据的处理,它提供的最核心的编程抽象,就是DataFrame
  2. DataFrame=RDD+Schema,它其实和关系型数据库中的表非常类似,DataFrame可以通过很多来源进行构建
  3. Spark1.3出现DataFrame,Spark1.6出现DataSet,在Spark2.0中两者统一,DataFrame等于DataSet[Row]
1.1、SparkSession
  1. 要使用Spark SQL,首先需要创建一个SparkSession对象
  2. SparkSession中包含SparkContext和SqlContext
  3. 使用SparkSession,可以从RDD,Hive表或者其他数据源创建DataFrame
代码实现
  1. 添加pom依赖

    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-sql_2.12</artifactId>
        <version>3.1.2</version>
    </dependency>
    
  2. 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()
      }
    }
    
  3. 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();
        }
    }
    
  4. req.json文件内容

    注意:这个json文件需要是压缩后的json文件,不能是格式化的

    {"name":"zhangsan","age":18}
    {"name":"lisi","age":21}
    {"name":"zzz","age":17}
    
  5. 代码演示效果

    image-20221129224836759

二、DataFrame常见算子操作

  1. printSchema
  2. show
  3. select
  4. filter\where
  5. groupBy
  6. 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中的数据

  1. 先将DataFrame注册为一个临时表
  2. 使用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();
​
    }
}

image-20221129225722085

四、RDD转换为DataFrame

Q:为什么要将RDD转换为DataFrame?

在实际工作中,我们可能会把hdfs上的一些日志数据加载进来,然后进行处理,比如清洗操作,最终变成结构化数据。我们希望对这些数据进行一些统计。

Saprk SQL支持两种方式将RDD转换为DataFrmae

  1. 反射方式
  2. 编程方式
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操作

  1. load操作主要用于加载数据,创建DataFrame
  2. save操作主要用于将DataFrame中的数据保存到文件中
  3. 如果不指定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

  1. SaveMode.ErrorIfExists(默认)

    如果目标位置已经存在数据,那么抛出一个异常

  2. SaveMode.Append

    如果目标位置已经存在数据,那么将数据追加进去

  3. SaveMode.Overwrite

    如果目标位置已经存在数据,那么就将已经存在的数据删除,用新数据进行覆盖

  4. 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