DataFrame数据运算操作

一. SQL风格操作

核心要义:将DataFrame 注册为一个临时视图view,然后就可以针对view直接执行各种sql临时视图有两种:session级别视图,global级别视图;
session级别视图是Session范围内有效的,Session退出后,表就失效了;
全局视图则在application级别有效;
注意使用全局表时需要全路径访问:global_temp.people

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

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

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()

二. DSL风格API语法

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

数据准备:

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

(1)基本select及表达式

/**
  * 逐行运算
  */
// 使用字符串表达"列"
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()

(2)条件过滤

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

(3)字段重命名

/**
  * 字段重命名
  */
// 对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()

(4)分组聚合

/**
  * 分组聚合
  */
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()

(5) 子查询

/**
  * 子查询
  * 相当于:
  * 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()

(6) Join关联查询

// 笛卡尔积
    //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")
      */

(7) 窗口分析函数调用

求每个城市中成绩最高的两个人的信息
如果用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实现:

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()
  }
}

三. RDD算子操作DataFrame

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

(1)从Row中抽取数据的方式1:通过字段的顺序索引号来获取

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:通过字段名称来获取

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:通过模式匹配进行数据抽取

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)
发布了5 篇原创文章 · 获赞 5 · 访问量 152

猜你喜欢

转载自blog.csdn.net/weixin_45687351/article/details/103812152
今日推荐