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表达,则可以把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无法使用