spark中dataframe,dataset,sparksql中的各種用法

package org.apache.spark.examples


import DsFilter.Student
import org.apache.spark.{HashPartitioner, Partitioner}
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.sql._
import org.apache.spark.sql.types.StructType

object DsFilter {

  def main(args: Array[String]): Unit = {
    System.setProperty("hadoop.home.dir","E:\\office\\hadoop-2.7.1") //如果讀取不到環境變量,設置這個
    val spark: SparkSession = SparkSession
      .builder
      .appName("Spark Examples")
      .master("local[*]") //我的機器是4core
      .getOrCreate()

    var list = Seq[Student]()
    for (i <- 1 to 10){
      list= list :+ Student("cc",i,i.toString)
      list= list :+ Student("chi",i,i.toString)
      list= list:+Student("zbf",i,i.toString)
      list= list:+Student("dd",i,i.toString)
    }//創建40條記錄
    val userList=Seq(User("cc",true),User("chi",true),User("dd",false),User("zbf",false))
    val rdd: RDD[Student] = spark.sparkContext.parallelize(list) //parallelize(list , numpartition) 這裏可以指定分區數,默認是hash分區,既然是hash分區那麼可能分配不均
    val partitions: Long = rdd.count()
    println(s"rdd中的數據量=${partitions}") // 40
    printRDDPartition(rdd)
    /**
     * 第0個分區-------(cc,1,1),(chi,1,1),(zbf,1,1),(dd,1,1),(cc,2,2),(chi,2,2),(zbf,2,2),(dd,2,2),(cc,3,3),(chi,3,3)
     * 第1個分區-------(zbf,3,3),(dd,3,3),(cc,4,4),(chi,4,4),(zbf,4,4),(dd,4,4),(cc,5,5),(chi,5,5),(zbf,5,5),(dd,5,5)
     * 第2個分區-------(cc,6,6),(chi,6,6),(zbf,6,6),(dd,6,6),(cc,7,7),(chi,7,7),(zbf,7,7),(dd,7,7),(cc,8,8),(chi,8,8)
     * 第3個分區-------(zbf,8,8),(dd,8,8),(cc,9,9),(chi,9,9),(zbf,9,9),(dd,9,9),(cc,10,10),(chi,10,10),(zbf,10,10),(dd,10,10)
     */
    val studentName: Array[String] = rdd.map(_.name).collect().distinct //獲取有多少個name, 目前是四個
    val hashparitionedRddTurple: RDD[(String, Student)] = rdd.map(x=>(x.name,x)).partitionBy(new HashPartitioner(4))
    val hashpartitonedRDd: RDD[Student] = hashparitionedRddTurple.map(_._2) //裏面的元素還是那40條 但是存放位置不一樣了
    println("hashpartitonedRDd分區後的數據")
    printRDDPartition(hashpartitonedRDd)
    /**
     * 第0個分區-------(cc,1,1),(chi,1,1),(dd,1,1),(cc,2,2),(chi,2,2),(dd,2,2),(cc,3,3),(chi,3,3),(dd,3,3),(cc,4,4),(chi,4,4),(dd,4,4),(cc,5,5),(chi,5,5),(dd,5,5),(cc,6,6),(chi,6,6),(dd,6,6),(cc,7,7),(chi,7,7),(dd,7,7),(cc,8,8),(chi,8,8),(dd,8,8),(cc,9,9),(chi,9,9),(dd,9,9),(cc,10,10),(chi,10,10),(dd,10,10)
     * 第1個分區-------
     * 第2個分區-------(zbf,1,1),(zbf,2,2),(zbf,3,3),(zbf,4,4),(zbf,5,5),(zbf,6,6),(zbf,7,7),(zbf,8,8),(zbf,9,9),(zbf,10,10)
     * 第3個分區-------
     */
    val paritionedRddTurple: RDD[(String, Student)] = rdd.map(x=>(x.name,x)).partitionBy(new MyPartition(studentName))
    val partitonedRDd: RDD[Student] = paritionedRddTurple.map(_._2) //裏面的元素還是那40條 但是存放位置不一樣了
    println("rdd分區後的數據")
    printRDDPartition(partitonedRDd)
    /**
     * 第0個分區-------(cc,1,1),(cc,2,2),(cc,3,3),(cc,4,4),(cc,5,5),(cc,6,6),(cc,7,7),(cc,8,8),(cc,9,9),(cc,10,10)
     * 第1個分區-------(chi,1,1),(chi,2,2),(chi,3,3),(chi,4,4),(chi,5,5),(chi,6,6),(chi,7,7),(chi,8,8),(chi,9,9),(chi,10,10)
     * 第2個分區-------(zbf,1,1),(zbf,2,2),(zbf,3,3),(zbf,4,4),(zbf,5,5),(zbf,6,6),(zbf,7,7),(zbf,8,8),(zbf,9,9),(zbf,10,10)
     * 第3個分區-------(dd,1,1),(dd,2,2),(dd,3,3),(dd,4,4),(dd,5,5),(dd,6,6),(dd,7,7),(dd,8,8),(dd,9,9),(dd,10,10)
     */
    /*   rdd中的數據其實看似有序,但是對於我們來說大部分無序,所以需要我們自己定義分區器,
    自定義分區的作用:一個分區做一件事,
                    ①比如我要輸出文件有序第一個文件是cc第二個是chi第三個是zbf,現在4個分區最後輸出文件是4個
                    ②每個分區數據有不同的邏輯 比如我想cc的age+1,chi的age+2
                    rdd.foreachPartition(iter=>{
                        val list: List[Student] = iter.toList
                       if(list.head.name.equals("cc")){}
                       if(list.head.name.equals("chi")){}
                    })
                    // 下面是lowb方法,先過濾出需要的rdd然後再處理
                    rdd.filter(_.name.equals("cc"))
                    rdd.filter(_.name.equals("chi"))
                    多嘴說一句
                    rdd.foreachPartition作用 比如我現在rdd是4個分區,數據是40個
                    如果涉及到數據庫連接建議用foreaparition 此時只需要創建4個連接,每個連接對應一個分區
                    如果用foreach 則需要創建40個連接 每個連接對應一條數據
                    有人會說我在最外面建立一個就好對應整個數據集合,不可能,這個涉及到序列化問題,簡單來說上面兩個連接都是在worker端建立
                    //driver端
                    建立連接 對應整個數據 然後driver到work相當於兩個世界,要把driver的數據到work相當於快遞要打包(序列化),目前連接不能序列化
                    foreach/partition(
                    //work端
                    //建立連接 對應一條數據或者一個分區
                    )
    */

    import spark.implicits._   //導入隱世轉換,因爲rdd是org.apache.spark.rdd包下的本身沒有toDF方法,所以導入spark.implicits._包,纔有rdd.todf
    val ds: Dataset[User] = spark.createDataset(userList)
    val rddToDf: DataFrame = rdd.toDF() //注意 rdd中是student樣例類所以可以直接轉化爲,如果是List(Seq("cc",1,"1"),Seq("cc",2,"2")) 要toDF("name","age","score")
    rddToDf.show(5) //action算子,自己測試時可以用,實際生產個人不建議頻繁用
    /**
     * +----+---+-----+
     * |name|age|score|
     * +----+---+-----+
     * |  cc|  1|    1|
     * | chi|  1|    1|
     * | zbf|  1|    1|
     * |  dd|  1|    1|
     * |  cc|  2|    2|
     * +----+---+-----+
     * only showing top 5 rows
     */
    //df的repartition
    val dfPartitionedRdd: RDD[Student] = rddToDf.repartition(4,new Column("name")).rdd.map(x=>Student(x.getString(0),x.getInt(1),x.getString(2)))
    println("rdd轉化成df後repartition後每個rdd分區")
    printRDDPartition(dfPartitionedRdd)
    /**  說明下,這裏df.repartition 根據name分區,是根據name 的hashcode值,以上述爲例
     * cc.hashcode=1 chi.hashcode=1 dd.hashcode=0 zbf.hashcode=3
     * 取餘後 dd分配到0分區,cc和chi分配到1分區,zbf分配到3分區,
     * df的reparition 和 rdd.partition都是hash分區
     * 第0個分區-------(dd,1,1),(dd,2,2),(dd,3,3),(dd,4,4),(dd,5,5),(dd,6,6),(dd,7,7),(dd,8,8),(dd,9,9),(dd,10,10)
     * 第1個分區-------(cc,1,1),(chi,1,1),(cc,2,2),(chi,2,2),(cc,3,3),(chi,3,3),(cc,4,4),(chi,4,4),(cc,5,5),(chi,5,5),(cc,6,6),(chi,6,6),(cc,7,7),(chi,7,7),(cc,8,8),(chi,8,8),(cc,9,9),(chi,9,9),(cc,10,10),(chi,10,10)
     * 第2個分區-------
     * 第3個分區-------(zbf,1,1),(zbf,2,2),(zbf,3,3),(zbf,4,4),(zbf,5,5),(zbf,6,6),(zbf,7,7),(zbf,8,8),(zbf,9,9),(zbf,10,10)
     */
    rddToDf.describe("name","age","score").show()
    /** //科學計數法
     * +-------+----+------------------+
     * |summary|name|               age|
     * +-------+----+------------------+
     * |  count|  30|                30|
     * |   mean|null|               5.5|
     * | stddev|null|2.9213837061606074|
     * |    min|  cc|                 1|
     * |    max| zbf|                10|
     * +-------+----+------------------+
     */
    println(" rddToDf.printSchema()=")
    rddToDf.printSchema()
    /**
     * root
     * |-- name: string (nullable = true)
     * |-- age: integer (nullable = false)
     * |-- score: string (nullable = true)
     */
    val column1: Column = rddToDf.apply("name")
    println(s"rddToDf.apply=${column1}")
    /**
     * rddToDf.apply=name
     */
    val schema: StructType = rddToDf.schema
    println(s" rddToDf.schema=${schema.mkString("|")}")
    /**
     * rddToDf.schema=StructField(name,StringType,true)|StructField(age,IntegerType,false)|StructField(score,StringType,true)
     */
    val columns: Array[String] = rddToDf.columns
    println(s" rddToDf.columns=${columns.mkString("|")}")
    /**
     * rddToDf.columns=name|age|score
     */
    val dtypes: Array[(String, String)] = rddToDf.dtypes
    println(s" rddToDf.dtypes=${dtypes.mkString("|")}")
    /**
     * rddToDf.dtypes=(name,StringType)|(age,IntegerType)|(score,StringType)
     */
    val column: Column = rddToDf.col("name")
    println(s"column=${column}")
    /**
     * column=name
     */
    rddToDf.explain() //目前層次未到 有點難看懂
    rddToDf.createTempView("Student") //創建一張臨時表
    val df: DataFrame = spark.sql("select * from Student")
    df.show(5)
    /**
     * +----+---+-----+
     * |name|age|score|
     * +----+---+-----+
     * |  cc|  1|    1|
     * | chi|  1|    1|
     * | zbf|  1|    1|
     * |  dd|  1|    1|
     * |  cc|  2|    2|
     * +----+---+-----+
     * only showing top 5 rows
     */

    import org.apache.spark.sql.functions._ //下面這種屬於DSL風格sql查詢,方法也是再functions._下的包裏纔有
    df.filter(col("name").equalTo("cc")).show(5)
    /**
     * +----+---+-----+
     * |name|age|score|
     * +----+---+-----+
     * |  cc|  1|    1|
     * |  cc|  2|    2|
     * |  cc|  3|    3|
     * |  cc|  4|    4|
     * |  cc|  5|    5|
     * +----+---+-----+
     */
    df.filter("age>5").show(5)
    /**
     * +----+---+-----+
     * |name|age|score|
     * +----+---+-----+
     * |  cc|  6|    6|
     * | chi|  6|    6|
     * | zbf|  6|    6|
     * |  dd|  6|    6|
     * |  cc|  7|    7|
     * +----+---+-----+
     * only showing top 5 rows
     */
      df.groupBy("name").agg(count(col("age")),avg(col("score"))).show(3)
    /**
     * +----+----------+----------+
     * |name|count(age)|avg(score)|
     * +----+----------+----------+
     * | zbf|        10|       5.5|
     * |  cc|        10|       5.5|
     * | chi|        10|       5.5|
     * +----+----------+----------+
     * only showing top 3 rows
     */
    df.groupBy("name").agg(max(col("age"))as("maxAge"),avg(col("score")).as("avgScore")).show(3)
    /** //取別名
     * +----+------+--------+
     * |name|maxAge|avgScore|
     * +----+------+--------+
     * | zbf|    10|     5.5|
     * |  cc|    10|     5.5|
     * | chi|    10|     5.5|
     * +----+------+--------+
     * only showing top 3 rows
     */
    df.cube("name","age").agg(sum("score").as("score")).orderBy(col("name"),col("score")).show(100)
    /**55條記錄 =40條記錄+4條name=null + 10條age=null +1條name=null&&age=null
     * select name ,age ,sum(score) as score from student groupby name ,age
     * union select null age ,sum(score) as score from student groupby age
     * union select name,null ,sum(score) as score from student groupby name
     * union select null,null,sum(score) as score from student groupby name
     * +----+----+-----+
     * |name| age|score|
     * +----+----+-----+
     * |null|   1|  4.0|
     * |null|   2|  8.0|
     * |null|   3| 12.0|
     * |null|   4| 16.0|
     * |null|   5| 20.0|
     * |null|   6| 24.0|
     * |null|   7| 28.0|
     * |null|   8| 32.0|
     * |null|   9| 36.0|
     * |null|  10| 40.0|
     * |null|null|220.0|
     * |  cc|   1|  1.0|
     * |  cc|   2|  2.0|
     * |  cc|   3|  3.0|
     * |  cc|   4|  4.0|
     * |  cc|   5|  5.0|
     * |  cc|   6|  6.0|
     * |  cc|   7|  7.0|
     * |  cc|   8|  8.0|
     * |  cc|   9|  9.0|
     * |  cc|  10| 10.0|
     * |  cc|null| 55.0|
     * | chi|   1|  1.0|
     * | chi|   2|  2.0|
     * | chi|   3|  3.0|
     * | chi|   4|  4.0|
     * | chi|   5|  5.0|
     * | chi|   6|  6.0|
     * | chi|   7|  7.0|
     * | chi|   8|  8.0|
     * | chi|   9|  9.0|
     * | chi|  10| 10.0|
     * | chi|null| 55.0|
     * |  dd|   1|  1.0|
     * |  dd|   2|  2.0|
     * |  dd|   3|  3.0|
     * |  dd|   4|  4.0|
     * |  dd|   5|  5.0|
     * |  dd|   6|  6.0|
     * |  dd|   7|  7.0|
     * |  dd|   8|  8.0|
     * |  dd|   9|  9.0|
     * |  dd|  10| 10.0|
     * |  dd|null| 55.0|
     * | zbf|   1|  1.0|
     * | zbf|   2|  2.0|
     * | zbf|   3|  3.0|
     * | zbf|   4|  4.0|
     * | zbf|   5|  5.0|
     * | zbf|   6|  6.0|
     * | zbf|   7|  7.0|
     * | zbf|   8|  8.0|
     * | zbf|   9|  9.0|
     * | zbf|  10| 10.0|
     * | zbf|null| 55.0|
     * +----+----+-----+
     */
    df.rollup("name","age").agg(sum("score").as("score")).orderBy(col("name"),col("score")).show(100)
    /**45條記錄=40 + 4條name分組 和1條 不分組
     * select name ,age ,sum(score) as score from student groupby name ,age
     * union select name,null ,sum(score) as score from student groupby name
     * union select null,null,sum(score) as score from student groupby name
     * !!!!!區別  個人理解!!!!!!
     * cube立方三維,借用kylin的思想就是從多個角度去看分組,如果我分組維度有3個,name,age,score,那麼先減去1個維度獲得【(name,age)(name,score)(age.score)】,然後減去2個維度獲得[(name),(age),(score)],再減去3個維度【】
     * rollup,向上 注意比如我name age 分組,(注意順序!!)那麼我還是逐漸減少分組維度,向上遞減,先減去age這個維度獲得4條記錄,然後減去name這個維度 獲得1條記錄
     * +----+----+-----+
     * |name| age|score|
     * +----+----+-----+
     * |null|null|220.0|
     * |  cc|   1|  1.0|
     * |  cc|   2|  2.0|
     * |  cc|   3|  3.0|
     * |  cc|   4|  4.0|
     * |  cc|   5|  5.0|
     * |  cc|   6|  6.0|
     * |  cc|   7|  7.0|
     * |  cc|   8|  8.0|
     * |  cc|   9|  9.0|
     * |  cc|  10| 10.0|
     * |  cc|null| 55.0|
     * | chi|   1|  1.0|
     * | chi|   2|  2.0|
     * | chi|   3|  3.0|
     * | chi|   4|  4.0|
     * | chi|   5|  5.0|
     * | chi|   6|  6.0|
     * | chi|   7|  7.0|
     * | chi|   8|  8.0|
     * | chi|   9|  9.0|
     * | chi|  10| 10.0|
     * | chi|null| 55.0|
     * |  dd|   1|  1.0|
     * |  dd|   2|  2.0|
     * |  dd|   3|  3.0|
     * |  dd|   4|  4.0|
     * |  dd|   5|  5.0|
     * |  dd|   6|  6.0|
     * |  dd|   7|  7.0|
     * |  dd|   8|  8.0|
     * |  dd|   9|  9.0|
     * |  dd|  10| 10.0|
     * |  dd|null| 55.0|
     * | zbf|   1|  1.0|
     * | zbf|   2|  2.0|
     * | zbf|   3|  3.0|
     * | zbf|   4|  4.0|
     * | zbf|   5|  5.0|
     * | zbf|   6|  6.0|
     * | zbf|   7|  7.0|
     * | zbf|   8|  8.0|
     * | zbf|   9|  9.0|
     * | zbf|  10| 10.0|
     * | zbf|null| 55.0|
     * +----+----+-----+
     */
    df.withColumn("newname",col("name")).show(3)
    /** 新增一列
     * +----+---+-----+-------+
     * |name|age|score|newname|
     * +----+---+-----+-------+
     * |  cc|  1|    1|     cc|
     * | chi|  1|    1|    chi|
     * | zbf|  1|    1|    zbf|
     * +----+---+-----+-------+
     * only showing top 3 rows
     */
    df.withColumn("newname",col("name")).select("name","age","score").show(3)
    /**
     * +----+---+-----+-------+
     * |name|age|score|newname|
     * +----+---+-----+-------+
     * |  cc|  1|    1|     cc|
     * | chi|  1|    1|    chi|
     * | zbf|  1|    1|    zbf|
     * +----+---+-----+-------+
     * only showing top 3 rows
     */
    df.select(col("name").equalTo("cc")).show(5)
    /**這個剛好和上面對應 cc chi zbf dd cc 剛好是true false false false true
     * +-----------+
     * |(name = cc)|
     * +-----------+
     * |       true|
     * |      false|
     * |      false|
     * |      false|
     * |       true|
     * +-----------+
     * only showing top 5 rows
     */
    df.select(col("age").lt(5)).show(5) //小於
    /**
     * +---------+
     * |(age < 5)|
     * +---------+
     * |     true|
     * |     true|
     * |     true|
     * |     true|
     * |     true|
     * +---------+
     * only showing top 5 rows
     */
    val dsBC: Broadcast[Dataset[User]] = spark.sparkContext.broadcast(ds)
    df.join(ds,df("name")===ds("name"),"outer").orderBy(col("score")).show(4)
    /**兩張表關聯
     * 'inner', 'outer', 'full', 'fullouter', 'full_outer', 'leftouter', 'left', 'left_outer',
     * 'rightouter', 'right', 'right_outer', 'leftsemi', 'left_semi', 'leftanti', 'left_anti', 'cross'
     * +----+---+-----+----+-----+
     * |name|age|score|name|shuai|
     * +----+---+-----+----+-----+
     * |  cc|  1|    1|  cc| true|
     * | chi|  1|    1| chi| true|
     * | zbf|  1|    1| zbf|false|
     * |  dd|  1|    1|  dd|false|
     * +----+---+-----+----+-----+
     * only showing top 10 rows
     */



  }

  //打印輸出
  def printRDDPartition(rdd:RDD[Student]): Unit ={
    val array: Array[Array[Student]] = rdd.glom().collect()
    for(i <- 0 until  array.size){
      println(s"第${i}個分區-------${array(i).map(x=>{
        (x.name,x.age,x.score)
      }).mkString(",")}")
    }
  }
  case class Student(name: String, age: Int,score:String){}
  case class User(name: String, shuai:Boolean){}
  class MyPartition(studentName:Array[String]) extends  Partitioner{
    override def numPartitions: Int = studentName.size //分區數
    override def getPartition(key: Any): Int = { //這個方法是那40條數據,每條數據都會把name 傳進來返回一個分區號
      val index: Array[(String, Int)] = studentName.zipWithIndex //給每個名字固定一個下標 =>其實也就是第幾個分區
      val tuples: Array[(String, Int)] = index.filter(_._1.equals(key.toString)) //在index數組中找到傳來名字的屬於哪個分區
      tuples.head._2 //head是因爲 數據去重了只有一條
    }
  }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章