让人

  • 动态表
  • 动态表和持续查询
  • 时间特性
  • 处理时间
  • 事件时间
  • 窗口
  • Group Windows
  • 滚动窗口
  • 滑动窗口
  • 会话窗口
  • 代码中的应用
  • Group Windows细节
  • GroupwindowTable
  • Over Windoweds
  • 函数
  • 内置函数
  • 自定义udf函数
  • 标量函数
  • 表函数
  • 聚合函数
  • 表聚合函数


把table转换成为datastream的时候,做了哪些操作?

可以吧datastream API和tableAPI集合起来一起用。甚至可以一段代码的处理程序里面,前面做datastream的转换,转换成某个datastream的时候,然后再转换成表,然后调用table API做一些操作。哪种方式更容易实现需求就用哪种方式。

需要注意的是,表转换成流的时候,没有更新模式。流输出了,不可能在输出的结果基础上再修改。流之前的数据已经输出到了下游,已经溜过去了。所以upsert模式在流里面没哟办法直接做转换。

sparksql在array查找对应元素 sparksql ifnull_mapreduce

追加模式:流中数据一条一条的插入到表中。
撤回模式,追加了一个撤回的信息,并不是真正的撤回流中数据,流就像水,流出去就是收不回。

sparksql在array查找对应元素 sparksql ifnull_flink_02

sparksql在array查找对应元素 sparksql ifnull_flink_03

sparksql在array查找对应元素 sparksql ifnull_apache_04

动态表

动态表是随着时间变化的。随着时间的到来而更新变化。 相当于在处理快照,不停的对快照进行做查询。

sparksql在array查找对应元素 sparksql ifnull_apache_05

动态表和持续查询

持续查询,state储存的是之前流数据中的聚合值,而不是之前大量的数据。后来的流中来了之后,再与之进行聚合。

sparksql在array查找对应元素 sparksql ifnull_flink_06

sparksql在array查找对应元素 sparksql ifnull_apache_07

sparksql在array查找对应元素 sparksql ifnull_大数据_08

外部系统支持这个模式,我们才可以指定这个模式。

sparksql在array查找对应元素 sparksql ifnull_大数据_09

sparksql在array查找对应元素 sparksql ifnull_mapreduce_10

时间特性

处理时间

sparksql在array查找对应元素 sparksql ifnull_大数据_11

流转换成表的时候,定义时间特性,这种用的最广泛。

sparksql在array查找对应元素 sparksql ifnull_flink_12

主要转换代码

//创建流执行环境。
    val env = StreamExecutionEnvironment.getExecutionEnvironment
        env.setParallelism(1)
 //默认是处理时间,但是习惯性的写一下,事件时间的话就是需要设置。       env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)

    //创建流表环境
    val tableEnv = StreamTableEnvironment.create(env)


    // 从文件中读取数据。
    val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"

    val inputStream: DataStream[String] = env.readTextFile(inpath)

    val dataStream: DataStream[SensorReading_timeAndWindowTest] = inputStream.map(data => {
      val strings = data.split(",")
      SensorReading_timeAndWindowTest(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
    })


    val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp,'pt.proctime)

sparksql在array查找对应元素 sparksql ifnull_大数据_13

这种就是得用blinplanner,引入的计算列的概念,计算出流中没有的列,并且添加进去。

sparksql在array查找对应元素 sparksql ifnull_大数据_14

事件时间

sparksql在array查找对应元素 sparksql ifnull_大数据_15

sparksql在array查找对应元素 sparksql ifnull_mapreduce_16

sparksql在array查找对应元素 sparksql ifnull_flink_17

sparksql在array查找对应元素 sparksql ifnull_大数据_18

窗口

sparksql在array查找对应元素 sparksql ifnull_mapreduce_19

Group Windows

sparksql在array查找对应元素 sparksql ifnull_flink_20

滚动窗口

参数是Tumble这个类,因为里面只有over这个方法,所以点省略了。同理后面的点也省略了。

sparksql在array查找对应元素 sparksql ifnull_mapreduce_21

滑动窗口

参数是Slide这个类,因为里面只有over这个方法,所以点省略了。同理后面的点也省略了。

sparksql在array查找对应元素 sparksql ifnull_mapreduce_22

会话窗口

sparksql在array查找对应元素 sparksql ifnull_大数据_23

代码中的应用

sparksql在array查找对应元素 sparksql ifnull_spark_24

Group Windows细节
GroupwindowTable

点进去

sparksql在array查找对应元素 sparksql ifnull_flink_25

Window方法有两个,先点进去看GroupWindowtTable的这个类。

sparksql在array查找对应元素 sparksql ifnull_flink_26

点进去发现,里面只有一个同名的方法,也就是只能调用这个方法。

sparksql在array查找对应元素 sparksql ifnull_spark_27

点进去这个类中。

sparksql在array查找对应元素 sparksql ifnull_spark_28

点击ctrl +F12,就可以发现做很多操作,如下的方法。

sparksql在array查找对应元素 sparksql ifnull_大数据_29

关于window里面的参数

直接传这个类,这个类本身是一个final class

sparksql在array查找对应元素 sparksql ifnull_spark_30

里面只有一个方法。over方法,类型是TumbleWithSize。

sparksql在array查找对应元素 sparksql ifnull_大数据_31

sparksql在array查找对应元素 sparksql ifnull_mapreduce_32

sparksql在array查找对应元素 sparksql ifnull_flink_33

点进去,然后看见as方法。

sparksql在array查找对应元素 sparksql ifnull_spark_34

sparksql在array查找对应元素 sparksql ifnull_大数据_35

继续点进去。这次终于继承了Groupwindow

sparksql在array查找对应元素 sparksql ifnull_flink_36

代码

package com.atguigu.tableApiTests


import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, _}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.Tumble
import org.apache.flink.table.api.scala._
import org.apache.flink.table.sources.wmstrategies.BoundedOutOfOrderTimestamps
import org.apache.flink.types.Row
case class SensorReading_timeAndWindowTest (id :String, timestamp: Long, temperature:Double)



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

    //创建流执行环境。
    val env = StreamExecutionEnvironment.getExecutionEnvironment
        env.setParallelism(1)
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

    //创建流表环境
    val tableEnv = StreamTableEnvironment.create(env)


    // 从文件中读取数据。
    val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"

    val inputStream: DataStream[String] = env.readTextFile(inpath)

    val dataStream: DataStream[SensorReading_timeAndWindowTest] = inputStream.map(data => {
      val strings = data.split(",")
      SensorReading_timeAndWindowTest(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
    }).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading_timeAndWindowTest](Time.seconds(1)) {
      override def extractTimestamp(element: SensorReading_timeAndWindowTest): Long = element.timestamp *1000
    } )

   //将流中数据转换为---流表
    val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime  as 'ts)  //起个别名,SQL中timestamp是关键字,防止冲突。



    // 1、group window

        // 1-1 table api

    val resultTable   =    SensorTable
           //每10秒统计一次滚动时间窗口
          .window(Tumble.over(10.seconds).on('ts)  as  'tw )
          .groupBy('id, 'tw)  //id为key,还有分组的窗口,作为分组条件。
          .select('id, 'id.count, 'temperature.avg,'tw.end)  //前面是常规的聚合操作,最后一个是调用当前窗口的结束时间,也就是关窗时间,也就是赋予了一些窗口的信息。


      // 1-2 SQL实现  用SQL的话,需要在环境中注册一下这个流表
    tableEnv.createTemporaryView("sensor",SensorTable)
   val resultSqlTable = tableEnv.sqlQuery(
     """
       | select
       |   id,
       |   count(id),
       |   avg(temperature),
       |   tumble_end(ts,interval '10' second)
       | from
       |   sensor
       |  group by
       |        id,
       |        tumble(ts,interval '10' second)
       |
       |
       |""".stripMargin
   ) //窗口的分组,因为是两个函数,tumble、tumble_end,没有办法起别名。



  //转换成流打印输出
    //如果没有处理迟到数据的话,那么就是不会撤回,只有追加操作,只输出一次,也就是不会撤回,用追加流和撤回流都是可以的。

    resultSqlTable.toAppendStream[Row].print("sql")
    resultTable.toAppendStream[Row].print("table")

    env.execute("12345")

  }

}

sparksql在array查找对应元素 sparksql ifnull_flink_37

Over Windoweds

和hive中的开窗函数是差不多的。

Groupwindow: 开了一个窗口,属于当前窗口的所有数据都放进去。都在里面做统计,最后每个窗口输出的是一个结果。
OverWindowedTable:每一行数据都要计算和他相邻的行去做聚合,然后得到一个结果。

sparksql在array查找对应元素 sparksql ifnull_mapreduce_38

tableAPI中的用法

sparksql在array查找对应元素 sparksql ifnull_apache_39

sparksql在array查找对应元素 sparksql ifnull_大数据_40

SQL中的用法

sparksql在array查找对应元素 sparksql ifnull_大数据_41

代码

package com.atguigu.tableApiTests


import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, _}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.{Over, Tumble}
import org.apache.flink.table.api.scala._
import org.apache.flink.table.sources.wmstrategies.BoundedOutOfOrderTimestamps
import org.apache.flink.types.Row
case class SensorReading_timeAndWindowTest (id :String, timestamp: Long, temperature:Double)


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

    //创建流执行环境。
    val env = StreamExecutionEnvironment.getExecutionEnvironment
        env.setParallelism(1)
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

    //创建流表环境
    val tableEnv = StreamTableEnvironment.create(env)


    // 从文件中读取数据。
    val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"

    val inputStream: DataStream[String] = env.readTextFile(inpath)

    val dataStream: DataStream[SensorReading_timeAndWindowTest] = inputStream.map(data => {
      val strings = data.split(",")
      SensorReading_timeAndWindowTest(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
    }).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading_timeAndWindowTest](Time.seconds(1)) {
      override def extractTimestamp(element: SensorReading_timeAndWindowTest): Long = element.timestamp *1000
    } )

   //将流中数据转换为---流表
    val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime  as 'ts)  //起个别名,SQL中timestamp是关键字,防止冲突。



    // 1、group window

        // 1-1 table api

    val resultTable   =    SensorTable
           //每10秒统计一次滚动时间窗口
          .window(Tumble.over(10.seconds).on('ts)  as  'tw )
          .groupBy('id, 'tw)  //id为key,还有分组的窗口,作为分组条件。
          .select('id, 'id.count, 'temperature.avg,'tw.end)  //前面是常规的聚合操作,最后一个是调用当前窗口的结束时间,也就是关窗时间,也就是赋予了一些窗口的信息。


      // 1-2 SQL实现  用SQL的话,需要在环境中注册一下这个流表
    tableEnv.createTemporaryView("sensor",SensorTable)
   val resultSqlTable = tableEnv.sqlQuery(
     """
       | select
       |   id,
       |   count(id),
       |   avg(temperature),
       |   tumble_end(ts,interval '10' second)
       | from
       |   sensor
       |  group by
       |        id,
       |        tumble(ts,interval '10' second)
       |
       |
       |""".stripMargin
   ) //窗口的分组,因为是两个函数,tumble、tumble_end,没有办法起别名。



  //转换成流打印输出
    //如果没有处理迟到数据的话,那么就是不会撤回,只有追加操作,只输出一次,也就是不会撤回,用追加流和撤回流都是可以的。

    resultSqlTable.toAppendStream[Row].print("sql")
    resultTable.toAppendStream[Row].print("table")



    // 2、over window  需求统计: 统计每个sensor每条数据,与之前的两行的平均温度。
    // 2-1 table api

     val overResultTable  =  SensorTable
             .window(Over.partitionBy('id).orderBy('ts).preceding(2.rows) as 'ow )
             .select('id, 'ts,  'id.count.over('ow),  'temperature.avg.over('ow) )


    // 2-2 SQL实现
   //上面环境中注册过了,直接用注册好的sensor
    val overResultSqlTable  =  tableEnv.sqlQuery(
        """
          |select
          |   id,
          |   ts,
          |   count(id)  over ow,
          |   avg(temperature)  over ow
          |from
          |    sensor
          |window ow as(
          |  partition by id
          |  order by ts
          |  rows between 2 preceding and current row
          |  )
          |""".stripMargin)


    overResultTable.toAppendStream[Row].print("overTable")
    overResultSqlTable.toAppendStream[Row].print("overSql")


    //   //看整体的结构
//    SensorTable.printSchema()
//
//    SensorTable.toAppendStream[Row].print()


    env.execute("12345")

  }

}

函数

内置函数

函数在不断的更新中,有的话直接用。

sparksql在array查找对应元素 sparksql ifnull_spark_42

sum0,是对于那种,没值的话,给一个默认的返回值0。

sparksql在array查找对应元素 sparksql ifnull_flink_43

自定义udf函数

sparksql在array查找对应元素 sparksql ifnull_mapreduce_44

标量函数

简单的可以认为是map操作。
里面的方法必须叫eval,必须是公开的。不能是private。

sparksql在array查找对应元素 sparksql ifnull_flink_45

代码

package com.atguigu.tableApiTests

import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.scala._
import org.apache.flink.table.functions.ScalarFunction
import org.apache.flink.types.Row
case class SensorReading (id :String, timestamp: Long, temperature:Double)

object udfTest {

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


    //创建流执行环境。
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

    //创建流表环境
    val tableEnv = StreamTableEnvironment.create(env)


    // 从文件中读取数据。
    val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"

    val inputStream: DataStream[String] = env.readTextFile(inpath)

    val dataStream: DataStream[SensorReading] = inputStream.map(data => {
      val strings = data.split(",")
      SensorReading(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
    }).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
      override def extractTimestamp(element: SensorReading): Long = element.timestamp *1000
    } )

    //将流中数据转换为---流表
    val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime  as 'ts)  //起个别名,SQL中timestamp是关键字,防止冲突。


   //调用自定义hash函数,对id进行hash运算。

    // 1、tableAPI

    //首先new一个udf的实例
    val hashCode = new HashCode(23)   // hashCode是new的对象,API和SQL都需要。

    val resultTable =  SensorTable.select('id, 'ts, hashCode('id) )


    //2、SQL

     //需要在表环境中注册udf
    tableEnv.createTemporaryView("sensor",SensorTable)
    tableEnv.registerFunction("hashCode",hashCode)  // hashCode是new的对象,API和SQL都需要。

  val resultSqlTable = tableEnv.sqlQuery(
    """
      |select
      |   id,
      |   ts,
      |   hashCode(id)
      | from
      |  sensor
      |""".stripMargin)



    resultTable.toAppendStream[Row].print("overTable")
    resultSqlTable.toAppendStream[Row].print("overSql")

    env.execute("12345")


  }

}

//自定义的hash函数
class HashCode(factor: Int)  extends ScalarFunction{

  def eval(s:String) :Int  = {

        s.hashCode * factor - 10000
  }


}
表函数

输出是多个行,可以理解为一张表。类似于侧写函数。
方法名字为eval,返回值为unit类型。
输出的类型在继承的类TableFunction后面中写。

sparksql在array查找对应元素 sparksql ifnull_spark_46

package com.atguigu.tableApiTests

import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, _}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.scala._
import org.apache.flink.table.functions.TableFunction
import org.apache.flink.types.Row

case class SensorReading (id :String, timestamp: Long, temperature:Double)

object tableFunctionTest {

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


    //创建流执行环境。
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

    //创建流表环境
    val tableEnv = StreamTableEnvironment.create(env)


    // 从文件中读取数据。
    val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"

    val inputStream: DataStream[String] = env.readTextFile(inpath)

    val dataStream: DataStream[SensorReading] = inputStream.map(data => {
      val strings = data.split(",")
      SensorReading(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
    }).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
      override def extractTimestamp(element: SensorReading): Long = element.timestamp *1000
    } )

    //将流中数据转换为---流表
    val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime  as 'ts)  //起个别名,SQL中timestamp是关键字,防止冲突。


    val split = new Split("_")   // new一个udf实例,传入下划线作分隔符。

     //1、tableAPI

    //原始的table和扩展的table,做一个侧向的连接
    //当前表中的每一行数据都要与 split的结果 进行一个连接操作,相当于自己和自己进行连接操作,相当于是一个笛卡尔积。
    val ResultTable =  SensorTable
     .joinLateral(split('id)  as ('word,'length))
     .select('id,'ts,'word,'length)


    //2、sql
   tableEnv.createTemporaryView("sensor",SensorTable)
   tableEnv.registerFunction("split",split)


 val ResultSqlTable =  tableEnv.sqlQuery(
      """
        |select
        |  id,
        |  ts,
        |  word,
        |  length
        |from
        |  sensor,lateral table( split(id) )  as splitid(word,length)
        |""".stripMargin)


    ResultTable.toAppendStream[Row].print("Table")
    ResultSqlTable.toAppendStream[Row].print("Sql")

    env.execute("12345")


  }

}

//  TableFunction[(String,Int)],里面的泛型是定义输出的类型
class Split(sep:String)  extends TableFunction[(String,Int)]{


  def eval(str:String)  ={

  str.split(sep).foreach(

         word => collect((word,word.length))

  )

  }

}
聚合函数

sparksql在array查找对应元素 sparksql ifnull_mapreduce_47

sparksql在array查找对应元素 sparksql ifnull_flink_48

package com.atguigu.tableApiTests

import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, _}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.scala._
import org.apache.flink.table.functions.AggregateFunction
import org.apache.flink.types.Row


object aggregateFunctionTest {


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

    //创建流执行环境。
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

    //创建流表环境
    val tableEnv = StreamTableEnvironment.create(env)


    // 从文件中读取数据。
    val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"

    val inputStream: DataStream[String] = env.readTextFile(inpath)

    val dataStream: DataStream[SensorReading] = inputStream.map(data => {
      val strings = data.split(",")
      SensorReading(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
    }).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
      override def extractTimestamp(element: SensorReading): Long = element.timestamp *1000
    } )

    //将流中数据转换为---流表
    val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime  as 'ts)  //起个别名,SQL中timestamp是关键字,防止冲突。


   //table api

    val avgTemp = new avgTemperature()

    val resultTable = SensorTable
      .groupBy('id)
      .aggregate(avgTemp('temperature) as 'avgtemp)
      .select('id, 'avgtemp)


   //SQL实现
    tableEnv.createTemporaryView("sensor",SensorTable)
    tableEnv.registerFunction("avgTemp",avgTemp)

  val sqltable = tableEnv.sqlQuery(
    """
      | select
      |   id ,avgTemp(temperature)
      |from
      |  sensor
      |group by id
      |""".stripMargin)


    resultTable.toRetractStream[Row].print("Table")
    sqltable.toRetractStream[Row].print("Sql")

    env.execute()
  }


}

//定义一个类,专门表示聚合的状态。
class AvgTempAcc{

  //这些变量是可变的,所以是用的是var而不是val。
  var sum:Double =0.0
  var count:Int =0

}

//自定义聚合函数,求每个传感器的平均温度值,保存状态(tempsum,tempcount),返回值为double型。
class  avgTemperature extends AggregateFunction[Double,AvgTempAcc]{


  override def getValue(accumulator: AvgTempAcc): Double = accumulator.sum /accumulator.count

  //保存结构。
  override def createAccumulator(): AvgTempAcc = new  AvgTempAcc


  //还要具体实现一个处理计算函数,accumulate,这个必须叫这个名字。
  //聚合结果的变量也是定义好的,必须叫accumulator。第二个位置是聚合的值,也就是创建对象的时候,需要输入的参数位置。
  //只是更新,不需要输出,所以返回值类型unit即可。
def accumulate(accumulator :AvgTempAcc, temp:Double)={

    accumulator.sum  += temp
    accumulator.count+=1

}

}
表聚合函数

sparksql在array查找对应元素 sparksql ifnull_spark_49

sparksql在array查找对应元素 sparksql ifnull_mapreduce_50

package com.atguigu.tableApiTests

import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, _}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.scala._
import org.apache.flink.table.functions.TableAggregateFunction
import org.apache.flink.types.Row
import org.apache.flink.util.Collector



object tableAggregationFunctionTest {

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

    //创建流执行环境。
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

    //创建流表环境
    val tableEnv = StreamTableEnvironment.create(env)


    // 从文件中读取数据。
    val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"

    val inputStream: DataStream[String] = env.readTextFile(inpath)

    val dataStream: DataStream[SensorReading] = inputStream.map(data => {
      val strings = data.split(",")
      SensorReading(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
    }).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
      override def extractTimestamp(element: SensorReading): Long = element.timestamp *1000
    } )

    //将流中数据转换为---流表
    val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime  as 'ts)  //起个别名,SQL中timestamp是关键字,防止冲突。



    //new 自定义的聚合函数
    val top2temping = new top2Temps()

    // table api

      val resulttable =   SensorTable
              .groupBy('id)
              .flatAggregate( top2temping('temperature) as  ('temp,'rank) ) //聚合函数输出的结果起个别名
              .select('id,'temp,'rank)  //这里就用到上面的别名

    resulttable.toRetractStream[Row].print("Table")

    env.execute()


  }


}


class top2Temp{

  var highestTemp :Double = Double.MinValue
  var secondHighTemp :Double = Double.MinValue

}


//自定义聚合函数,实现top n,这里提取所有 温度值的最高的两个温度。输出(temp,rank)

class top2Temps extends TableAggregateFunction[(Double,Int),top2Temp]{


  override def createAccumulator(): top2Temp = new top2Temp()



  //实现计算聚合结果的函数 accumulate

def accumulate(accumulator: top2Temp,temp:Double) ={

   if(temp>accumulator.highestTemp) {
     accumulator.secondHighTemp = accumulator.highestTemp
     accumulator.highestTemp = temp
   }else if(temp>accumulator.secondHighTemp){

     //大于第二温度小于第一温度,直接替换第二温度。
     accumulator.secondHighTemp = temp
   }
  
}

  //实现一个输出结果的方法,最终处理完表中所有数据的时候进行调用。
  //collect输出,不需要返回值。
  def emitValue(acc:top2Temp,out:Collector[(Double,Int)]) ={

    out.collect((acc.highestTemp,1))
    out.collect((acc.secondHighTemp,2))

  }

}