Spark SQL

本文详细介绍了Spark SQL中DataFrame的纯SQL操作和DSL风格API,包括临时视图、子查询、Join、Union和窗口分析函数。同时讨论了Dataset与RDD的混编,如何从RDD创建DataFrame和Dataset,以及三者之间的转换方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

3.3 DF数据运算操作

3.3.1 纯SQL操作

核心要义:将DataFrame 注册为一个临时视图view,然后就可以针对view直接执行各种sql

临时视图有两种:session级别视图,global级别视图;

session级别视图是Session范围内有效的,Session退出后,表就失效了;

全局视图则在application级别有效;

注意使用全局表时需要全路径访问:global_temp.people

Scala
// application全局有效
df.createGlobalTempView("stu")
spark.sql(
  """
    |select * from global_temp.stu a order by a.score desc
  """.stripMargin)
    .show()

Scala
// session有效
df.createTempView("s")
spark.sql(
  """
    |select * from s order by score
  """.stripMargin)
  .show()

Scala
val spark2 = spark.newSession()
// 全局有效的view可以在session2中访问
spark2.sql("select id,name from global_temp.stu").show()
// session有效的view不能在session2中访问
spark2.sql("select id,name from s").show()

以上只是对语法的简单示例,可以扩展到任意复杂的sql

挑战一下 ?

求出每个城市中,分数最高的学生信息;

Go go go !

3.3.2 DSL风格API(TableApi)语法

DSL风格API,就是用编程api的方式,来实现sql语法

DSL:特定领域语言

dataset的tableApi有一个特点:运算后返回值必回到dataframe

因为select后,得到的结果,无法预判返回值的具体类型,只能用通用的Row封装

数据准备

Scala
val df = spark.read
  .option("header", true)
  .option("inferSchema", true)
  .csv("data_ware/demodata/stu.csv")

(1)基本select及表达式

Scala
/**
  * 逐行运算
  */
// 使用字符串表达"列"
df.select("id","name").show()

// 如果要用字符串形式表达sql表达式,应该使用selectExpr方法
df.selectExpr("id+1","upper(name)").show
// select方法中使用字符串sql表达式,会被视作一个列名从而出错
// df.select("id+1","upper(name)").show()

import spark.implicits._
// 使用$符号创建Column对象来表达"列"
df.select($"id",$"name").show()

// 使用单边单引号创建Column对象来表达"列"
df.select('id,'name).show()

// 使用col函数来创建Column对象来表达"列"
import org.apache.spark.sql.functions._
df.select(col("id"),col("name")).show()

// 使用Dataframe的apply方法创建Column对象来表达列
df.select(df("id"),df("name")).show()

// 对Column对象直接调用Column的方法,或调用能生成Column对象的functions来实现sql中的运算表达式
df.select('id.plus(2).leq("4").as("id2"),upper('name)).show()
df.select('id+2 <= 4 as "id2",upper('name)).show()

(3)字段重命名

Scala
/**
  * 字段重命名
  */
// 对column对象调用as方法
df.select('id as "id2",$"name".as("n2"),col("age") as "age2").show()

// 在selectExpr中直接写sql的重命名语法
df.selectExpr("cast(id as string) as id2","name","city").show()

// 对dataframe调用withColumnRenamed方法对指定字段重命名
df.select("id","name","age").withColumnRenamed("id","id2").show()

// 对dataframe调用toDF对整个字段名全部重设
df.toDF("id2","name","age","city2","score").show()

(2)条件过滤

Scala
/**
  * 逐行过滤
  */
df.where("id>4 and score>95")
df.where('id > 4 and 'score > 95).select("id","name","age").show()

(4)分组聚合

Scala
/**
  * 分组聚合
  */
df.groupBy("city").count().show()
df.groupBy("city").min("score").show()
df.groupBy("city").max("score").show()
df.groupBy("city").sum("score").show()
df.groupBy("city").avg("score").show()

df.groupBy("city").agg(("score","max"),("score","sum")).show()
df.groupBy("city").agg("score"->"max","score"->"sum").show()

3.3.2.1 子查询

Scala
/**
  * 子查询
  * 相当于:
  * select
  * *
  * from
  * (
  *   select
  *   city,sum(score) as score
  *   from stu
  *   group by city
  * ) o
  * where score>165
  */
df.groupBy("city")
  .agg(sum("score") as "score")
  .where("score > 165")
  .select("city", "score")
  .show()

3.3.2.2 Join关联查询

Scala
package cn.doitedu.sparksql

import org.apache.spark.sql.{DataFrame, SparkSession}

/**
  *  用 DSL风格api来对dataframe进行运算
 */
object Demo14_DML_DSLapi {
  def main(args: Array[String]): Unit = {

    val spark = SparkUtil.getSpark()
    import spark.implicits._

    val df = spark.read.option("header",true).csv("data/stu2.csv")
    val df2 = spark.read.option("header",true).csv("data/stu22.csv")

    /**
      * SQL中都有哪些运算?
      *    1. 查询字段(id)
      *    2. 查询表达式(算术运算,函数  age+10, upper(name))
      *    3. 过滤
      *    4. 分组聚合
      *    5. 子查询
      *    6. 关联查询
      *    7. union查询
      *    8. 窗口分析
      *    9. 排序
      */

    //selectOp(spark,df)
    //whereOp(spark,df)
    //groupbyOp(spark,df)
    joinOp(spark,df,df2)

    spark.close()
  }
  
    /**
    * 关联查询
    * @param spark
    * @param df1
    */
  def joinOp(spark:SparkSession,df1:DataFrame,df2:DataFrame): Unit ={

    // 笛卡尔积
    //df1.crossJoin(df2).show()

    // 给join传入一个连接条件; 这种方式,要求,你的join条件字段在两个表中都存在且同名
    df1.join(df2,"id").show()

    // 传入多个join条件列,要求两表中这多个条件列都存在且同名
    df1.join(df2,Seq("id","sex")).show()


    // 传入一个自定义的连接条件表达式
    df1.join(df2,df1("id") + 1 === df2("id")).show()


    // 还可以传入join方式类型: inner(默认), left ,right, full ,left_semi, left_anti
    df1.join(df2,df1("id")+1 === df2("id"),"left").show()
    df1.join(df2,Seq("id"), "right").show()


    /**
      * 总结:
      * join方式:   joinType: String
      * join条件:
      * 可以直接传join列名:usingColumn/usingColumns : Seq(String) 注意:右表的join列数据不会出现结果中
      *  可以传join自定义表达式: Column.+(1) === Column     df1("id")+1 === df2("id")
      */
  }
}

3.3.2.3 Union操作

Scala
Sparksql中的union,其实是union all
df.union(df).show()

3.3.2.4 窗口分析函数调用

测试数据:

Plain Text
id,name,age,sex,city,score
1,张飞,21,M,北京,80
2,关羽,23,M,北京,82
7,周瑜,24,M,北京,85
3,赵云,20,F,上海,88
4,刘备,26,M,上海,83
8,孙权,26,M,上海,78
5,曹操,30,F,深圳,90.8
6,孔明,35,F,深圳,77.8
9,吕布,28,M,深圳,98

求每个城市中成绩最高的两个人的信息,如果用sql写:

SQL
select 
id,name,age,sex,city,score
from 
  (
     select
       id,name,age,sex,city,score,
       row_number()  over(partition by city order by score desc) as rn
     from t
  ) o
where rn<=2

DSL风格的API实现:

Scala
package cn.doitedu.sparksql

import org.apache.spark.sql.expressions.Window

/**
 *   用dsl风格api实现sql中的窗口分析函数
 */
object Demo15_DML_DSLAPI_WINDOW {

  def main(args: Array[String]): Unit = {

    val spark = SparkUtil.getSpark()

    val df = spark.read.option("header",true).csv("data/stu2.csv")

    import spark.implicits._
    import org.apache.spark.sql.functions._


    val window = Window.partitionBy('city).orderBy('score.desc)

    df.select('id,'name,'age,'sex,'city,'score,row_number().over(window) as "rn")
      .where('rn <= 2)
      .drop("rn") // 最后结果中不需要rn列,可以drop掉这个列
      .select('id,'name,'age,'sex,'city,'score)  // 或者用select指定你所需要的列
      .show()

    spark.close()
  }
}

Dataset提供与RDD类似的编程算子,即map/flatMap/reduceByKey等等,不过本方式使用略少:

  • 如果方便用sql表达的逻辑,首选sql
  • 如果不方便用sql表达,则可以把Dataset转成RDD后使用 RDD的算子

直接在Dataset上调用类似RDD风格算子的代码示例如下:

  

Scala
 /**
     * 四、 dataset/dataframe 调 RDD算子
     *
     * dataset调rdd算子,返回的还是dataset[U], 不过需要对应的 Encoder[U]
     *
     */
    val ds4: Dataset[(Int, String, Int)] = ds2.map(p => (p.id, p.name, p.age + 10))   // 元组有隐式Encoder自动传入
    val ds5: Dataset[JavaPerson] = ds2.map(p => new JavaPerson(p.id,p.name,p.age*2))(Encoders.bean(classOf[JavaPerson])) // Java类没有自动隐式Encoder,需要手动传

    val ds6: Dataset[Map[String, String]] = ds2.map(per => Map("name" -> per.name, "id" -> (per.id+""), "age" -> (per.age+"")))
    ds6.printSchema()
    /**
     * root
         |-- value: map (nullable = true)
         |    |-- key: string
         |    |-- value: string (valueContainsNull = true)
     */
    // 从ds6中查询每个人的姓名
    ds6.selectExpr("value['name']")


    // dataframe上调RDD算子,就等价于 dataset[Row]上调rdd算子
    val ds7: Dataset[(Int, String, Int)] = frame.map(row=>{
      val id: Int = row.getInt(0)
      val name: String = row.getAs[String]("name")
      val age: Int = row.getAs[Int]("age")
      (id,name,age)
    })
    
    // 利用模式匹配从row中抽取字段数据
    val ds8: Dataset[Per] = frame.map({
      case Row(id:Int,name:String,age:Int) => Per(id,name,age*10)
    })

3.5 Dataset与RDD混编

3.5.1 DataSet和Dataframe的区别

狭义上,Dataset中装的是用户自定义的类型

那么在抽取数据时,比较方便 stu.id 且类型会得到编译时检查

狭义上,dataframe中装的是Row(框架内置的一个通用类型)

那么在抽取数据时,不太方便,得通过脚标,或者字段名,而且还得强转

Scala
val x:Any =  row.get(1)
val x:Double = row.getDouble(1)
val x:Double = row.getAs[Double](“salary”)

Scala
/**
  * dataset存在的意义?
  * 意义要从它的特点说起:
  * ds的特点是,可以存储各种自定义类型,自定义类型中,各字段是有类型约束(所以ds是强类型约束的)
  * df只能存储row类型,而row类型中的字段是没有“类型约束“,全是any(所以df是弱类型约束的)
  */
ds.map(bean => {
  // val id:String = bean.id  // 提取数据时不会产生类型匹配错误,编译时就会检查

})


val _df: Dataset[Row] = ds.toDF()
_df.map(row => {
  val name: Int = row.getInt(1) // 明明类型不匹配,但是编译时无法检查,运行时才会抛异常
})

3.5.3 DataFrame/dataset转成RDD后取数

要义:有些运算场景下,通过SQL语法实现计算逻辑比较困难,可以将DataFrame转成RDD算子来操作,而DataFrame中的数据是以RDD[Row]类型封装的,因此,要对DataFrame进行RDD算子操作,只需要掌握如何从Row中解构出数据即可

示例数据stu.csv

Plain Text
id,name,age,city,score
1,张飞,21,北京,80.0
2,关羽,23,北京,82.0
3,赵云,20,上海,88.6
4,刘备,26,上海,83.0
5,曹操,30,深圳,90.0

(1)从Row中取数方式1:索引号

 

示例代码

Scala
val rdd: RDD[Row] = df.rdd
rdd.map(row=>{
  val id = row.get(0).asInstanceOf[Int]
  val name = row.getString(1)
  (id,name)
}).take(10).foreach(println)

(2)从Row中取数方式2:字段名

Scala
rdd.map(row=>{
  val id = row.getAs[Int]("id")
  val name = row.getAs[String]("name")
  val age = row.getAs[Int]("age")
  val city = row.getAs[String]("city")
  val score = row.getAs[Double]("score")
  (id,name,age,city,score)
}).take(10).foreach(println)

(3)从Row中取数方式3:模式匹配

Scala
rdd.map({
  case Row(id: Int, name: String, age: Int, city: String, score: Double)
  => {
    // do anything
    (id,name,age,city,score)
  }
}).take(10).foreach(println)

(4)完整示例

Scala
package cn.doitedu.sparksql

import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{Dataset, Row}
import org.apache.spark.sql.types.{DoubleType, IntegerType, StringType, StructField, StructType}

/**
  *   有些场景下,逻辑不太方便用sql去实现,可能需要将dataframe退化成RDD来计算
  *   示例需求:  求每种性别的成绩总和
 */
object Demo16_DML_RDD {
  def main(args: Array[String]): Unit = {

    val spark = SparkUtil.getSpark()
    // val schema = new StructType(Array(StructField("id",IntegerType),StructField("name",StringType)))

    // val schema = new StructType((StructField("id",IntegerType):: StructField("name",StringType) :: Nil).toArray)

    val schema = new StructType()
      .add("id",IntegerType)
      .add("name",StringType)
      .add("age",IntegerType)
      .add("sex",StringType)
      .add("city",StringType)
      .add("score",DoubleType)

    val df = spark.read.schema(schema).option("header",true).csv("data/stu2.csv")

    // 可以直接在dataframe上用map等rdd算子
    // 框架会把算子返回的结果RDD 再转回dataset,需要一个能对RDD[T]进行解析的Encoder[T]才行
    // 好在大部分T类型都可以有隐式的Encoder来支持
    import spark.implicits._
    val ds2: Dataset[(Int, String)] = df.map(row=>{
      val id = row.getAs[Int]("id")
      val name = row.getAs[String]("name")
      (id,name)
    })


    // dataframe中取出rdd后,就是一个RDD[Row]
    val rd: RDD[Row] = df.rdd
    // 从Row中取数据,就可以变成任意你想要的类型
    val rdd2: RDD[(Int, String, Int, String, String, Double)] = rd.map(row=>{

      // dataframe是一种弱类型结构(在编译时无法检查类型,因为数据被装在了一个array[any]中)
      // val id = row.getDouble(1) // 如果类型取错,编译时是无法检查的,运行时才会报错

      // 可以根据字段的脚标去取
      val id: Int = row.getInt(0)
      val name: String = row.getString(1)
      val age: Int = row.getAs[Int](2)

      // 可以根据字段名称去取
      val sex: String = row.getAs[String]("sex")
      val city: String = row.getAs[String]("city")
      val score: Double = row.getAs[Double]("score")

      (id,name,age,sex,city,score)
    })


    /**
      * 用模式匹配从Row中抽取数据
      * 效果跟上面的方法是一样的,但是更简洁!
      */
    val rdd22 = rd.map({
      case Row(id:Int,name:String,age:Int,sex:String,city:String,score:Double)=>{
        (id,name,age,sex,city,score)
      }
    })

    // 后续就跟dataframe没关系了,跟以前的rdd是一样的了
    val res: RDD[(String, Double)] = rdd22.groupBy(tp=>tp._4).mapValues(iter=>{
      iter.map(_._6).sum
    })

    res.foreach(println)

    spark.close()
  }
}

3.5.4从RDD创建DataFrame

准备测试用的数据和RDD

后续示例都起源于如下RDD

数据文件:doit_stu.txt

Plain Text
1,张飞,21,北京,80.0
2,关羽,23,北京,82.0
3,赵云,20,上海,88.6
4,刘备,26,上海,83.0
5,曹操,30,深圳,90.0

创建RDD:

Scala
val rdd:RDD[String] = spark.sparkContext.textFile("data_ware/demodata/stu.txt")

(1)从RDD[Case class类]创建DataFrame

注:定义一个case class来封装数据,如下,Stu是一个case class类

Scala
val rdd:RDD[String] = spark.sparkContext.textFile("data_ware/demodata/stu.txt")

示例代码:

Scala
val rddStu: RDD[Stu]  = rdd
  // 切分字段
  .map(_.split(","))
  // 将每一行数据变形成一个多元组tuple
  .map(arr => Stu(arr(0).toInt, arr(1), arr(2).toInt, arr(3), arr(4).toDouble))
// 创建DataFrame
val df = spark.createDataFrame(rddStu)
df.show()

结果如下:

Plain Text
+---+-------+---+-----+-----+
| id|name   |age|city |score|
+---+-------+---+-----+-----+
|  1|  张飞| 21|  北京| 80.0|
|  2|  关羽| 23|  北京| 82.0|
|  3|  赵云| 20|  上海| 88.6|
|  4|  刘备| 26|  上海| 83.0|
|  5|  曹操| 30|  深圳| 90.0|
+---+------+---+------+-----+

可以发现,框架成功地从case class的类定义中推断出了数据的schema:字段类型和字段名称

Schema获取手段:反射

当然,还有更简洁的方式,利用框架提供的隐式转换

Scala
// 更简洁办法
import spark.implicits._
val df = rddStu.toDF

(2)从RDD[Tuple]创建DataFrame

Scala
val rddTuple: RDD[(Int, String, Int, String, Double)] = rdd
  // 切分字段
  .map(_.split(","))
  // 将每一行数据变形成一个多元组tuple
  .map(arr => (arr(0).toInt, arr(1), arr(2).toInt, arr(3), arr(4).toDouble))

//创建DataFrame
val df = spark.createDataFrame(rddTuple)
df.printSchema() // 打印schema信息
df.show()

结果如下:

Plain Text
root
 |-- _1: integer (nullable = false)
 |-- _2: string (nullable = true)
 |-- _3: integer (nullable = false)
 |-- _4: string (nullable = true)
 |-- _5: double (nullable = false)
 
 
 +---+-----+---+---+-----+
| _1| _2  | _3| _4|  _5 |
+---+-----+---+---+-----+
|  1| 张飞| 21| 北京|80.0|
|  2| 关羽| 23| 北京|82.0|
|  3| 赵云| 20| 上海|88.6|
|  4| 刘备| 26| 上海|83.0|
|  5| 曹操| 30| 深圳|90.0|
+---+---+---+---+----+

从结果中可以发现一个问题:框架从tuple元组结构中,对schema的推断,也是成功的,只是字段名是tuple中的数据访问索引。

当然,还有更简洁的方式,利用框架提供的隐式转换可以直接调用toDF创建,并指定字段名

Scala
// 更简洁办法
import spark.implicits._
val df2 = rddTuple.toDF("id","name","age","city","score")

(3)从RDD[JavaBean]创建DataFrame

注:此处所说的Bean,指的是用java定义的bean

Java
public class Stu2 {
    private int id;
    private String name;
    private int age;
    private String city;
    private double score;

    public Stu2(int id, String name, int age, String city, double score) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.city = city;
        this.score = score;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {}

示例代码:

Scala
val rddBean: RDD[Stu2] = rdd
  // 切分字段
  .map(_.split(","))
  // 将每一行数据变形成一个JavaBean
  .map(arr => new Stu2(arr(0).toInt,arr(1),arr(2).toInt,arr(3),arr(4).toDouble))
val df = spark.createDataFrame(rddBean,classOf[Stu2])
df.show()

结果如下:

Plain Text
+---+-----+---+-----+----+
|age|city | id|name |score|
+---+-----+---+-----+----+
|  1| 张飞| 21| 北京|80.0|
|  2| 关羽| 23| 北京|82.0|
|  3| 赵云| 20| 上海|88.6|
|  4| 刘备| 26| 上海|83.0|
|  5| 曹操| 30| 深圳|90.0|
+---+-----+---+-----+----+

注:RDD[JavaBean]在spark.implicits._中没有toDF的支持

(4)从RDD[普通Scala类]中创建DataFrame

注:此处的普通类指的是scala中定义的非case class的类

框架在底层将其视作java定义的标准bean类型来处理

而scala中定义的普通bean类,不具备字段的java标准getters和setters,因而会处理失败

可以如下处理来解决

普通scala bean类定义:

Scala
class Stu3(
            @BeanProperty
            val id: Int,
            @BeanProperty
            val name: String,
            @BeanProperty
            val age: Int,
            @BeanProperty
            val city: String,
            @BeanProperty
            val score: Double)

示例代码:

Scala
val rddStu3: RDD[Stu3] = rdd
  // 切分字段
  .map(_.split(","))
  // 将每一行数据变形成一个普通Scala对象
  .map(arr => new Stu3(arr(0).toInt, arr(1), arr(2).toInt, arr(3), arr(4).toDouble))
val df = spark.createDataFrame(rddStu3, classOf[Stu3])
df.show()

(5)从RDD[Row]中创建DataFrame

注:DataFrame中的数据,本质上还是封装在RDD中,而RDD[ T ]总有一个T类型,DataFrame内部的RDD中的元素类型T即为框架所定义的Row类型;

Scala
val rddRow = rdd
  // 切分字段
  .map(_.split(","))
  // 将每一行数据变形成一个Row对象
  .map(arr => Row(arr(0).toInt, arr(1), arr(2).toInt, arr(3), arr(4).toDouble))

val schema = new StructType()
  .add("id", DataTypes.IntegerType)
  .add("name", DataTypes.StringType)
  .add("age", DataTypes.IntegerType)
  .add("city", DataTypes.StringType)
  .add("score", DataTypes.DoubleType)

val df = spark.createDataFrame(rddRow,schema)
df.show()

(6)从RDD[set/seq/map]中创建DataFrame

版本2.2.0,新增了对SET/SEQ的编解码支持

版本2.3.0,新增了对Map的编解码支持

Scala
object Demo7_CreateDF_SetSeqMap {

  def main(args: Array[String]): Unit = {

    val spark = SparkSession.builder().appName("").master("local[*]").getOrCreate()

    val seq1 = Seq(1,2,3,4)
    val seq2 = Seq(11,22,33,44)

    val rdd: RDD[Seq[Int]] = spark.sparkContext.parallelize(List(seq1,seq2))

    import spark.implicits._
    val df = rdd.toDF()

    df.printSchema()
    df.show()


    df.selectExpr("value[0]","size(value)").show()


    /**
      * set类型数据rdd的编解码
      */
    val set1 = Set("a","b")
    val set2 = Set("c","d","e")
    val rdd2: RDD[Set[String]] = spark.sparkContext.parallelize(List(set1,set2))

    val df2 = rdd2.toDF("members")
    df2.printSchema()
    df2.show()


    /**
      * map类型数据rdd的编解码
      */

    val map1 = Map("father"->"mayun","mother"->"tangyan")
    val map2 = Map("father"->"huateng","mother"->"yifei","brother"->"sicong")
    val rdd3: RDD[Map[String, String]] = spark.sparkContext.parallelize(List(map1,map2))

    val df3 = rdd3.toDF("jiaren")
    df3.printSchema()
    df3.show()

    df3.selectExpr("jiaren['mother']","size(jiaren)","map_keys(jiaren)","map_values(jiaren)")
        .show(10,false)


    spark.close()
  }
}

set/seq 结构出来的字段类型为 : array

Map 数据类型解构出来的字段类型为:map

3.5.5 从RDD创建DataSet

 

(1)从RDD[Case class类]创建Dataset

Scala
val rdd: RDD[Person] = spark.sparkContext.parallelize(Seq(
  Person(1, "zs"),
  Person(2, "ls")
))

import spark.implicits._

// case class 类型的rdd,转dataset
val ds: Dataset[Person] = spark.createDataset(rdd)
val ds2: Dataset[Person] = rdd.toDS()
ds.printSchema()
ds.show()

Scala
/**
  * 创建一个javaBean 的RDD
  * 隐式转换中没有支持好对javabean的encoder机制
  * 需要自己传入一个encoder
  * 可以构造一个简单的encoder,具备序列化功能,但是不具备字段解构的功能
  * 但是,至少能够把一个RDD[javabean] 变成一个 dataset[javabean]
  * 后续可以通过rdd的map算子将数据从对象中提取出来,组装成tuple元组,然后toDF即可进入sql空间
  */
val rdd2: RDD[JavaStu] = spark.sparkContext.parallelize(Seq(
  new JavaStu(1,"a",18,"上海",99.9),
  new JavaStu(2,"b",28,"北京",99.9),
  new JavaStu(3,"c",38,"西安",99.9)
))


val encoder = Encoders.kryo(classOf[JavaStu])
val ds2: Dataset[JavaStu] = spark.createDataset(rdd2)(encoder)

val df2: Dataset[Row] = ds2.map(stu => {
  (stu.getId, stu.getName, stu.getAge)
}).toDF("id", "name", "age")
ds2.printSchema()
ds2.show()
df2.show()

3.3.2.5 从RDD[其他类]创建Dataset

Scala
/**
  * 将一个RDD[Map] 变成  Dataset[Map]
  * 2.3.0版才支持
  *
 */

val rdd3: RDD[Map[String, String]] = spark.sparkContext.parallelize(Seq(
  Map("id"->"1","name"->"zs1"),
  Map("id"->"2","name"->"zs2"),
  Map("id"->"3","name"->"zs3")
))

val ds3: Dataset[Map[String, String]] = rdd3.toDS()
ds3.printSchema()
ds3.show()

3.6 RDD/DS/DF互转

RDD、DataFrame、Dataset三者有许多共性,有各自适用的场景常常需要在三者之间转换

DataFrame/Dataset转RDD:

Scala
val rdd1:RDD[Row]=testDF.rdd
val rdd2:RDD[T]=testDS.rdd

RDD转DataFrame:

Scala
import spark.implicits._
val testDF = rdd.map {line=>
      (line._1,line._2)
    }.toDF("col1","col2")

一般用元组把一行的数据写在一起,然后在toDF中指定字段名

RDD转Dataset:

Scala
import spark.implicits._
case class Person(col1:String,col2:Int)extends Serializable //定义字段名和类型
val testDS:Dataset[Person] = rdd.map {line=>
      Person(line._1,line._2)
    }.toDS

可以注意到,定义每一行的类型(case class)时,已经给出了字段名和类型,后面只要往case class里面添加值即可

Dataset转DataFrame:

这个也很简单,因为只是把case class封装成Row

Scala
import spark.implicits._
val testDF:Dataset[Row] = testDS.toDF

DataFrame转Dataset:

Scala
import spark.implicits._
case class Coltest(col1:String,col2:Int)extends Serializable //定义字段名和类型
val testDS = testDF.as[Coltest]

这种方法就是在给出每一列的类型后,使用as方法,转成Dataset,这在数据类型是DataFrame又需要针对各个字段处理时极为方便。

在使用一些特殊的操作时,一定要加上 import spark.implicits._ 不然toDF、toDS无法使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值