80-99-flink-tableapi-filnksql

80-flink-tableapi-filnksql:

Table API和Flink SQL

Flink-Table API 和 Flink SQL简介 | 新老版本Flink批流处理对比 | 读取文件和Kafka消费数据 | API 和 SQL查询表:https://blog.csdn.net/qq_40180229/article/details/106457648

flink-Table&sql-碰到的几个问题记录:https://blog.csdn.net/weixin_41956627/article/details/110050094

1 概述

  • Flink 对批处理和流处理,提供了统一的上层 API
  • Table API 是一套内嵌在 Java 和 Scala 语言中的查询API,它允许以非常直观的方式组合来自一些关系运算符的查询
  • Flink 的 SQL 支持基于实现了 SQL 标准的 Apache Calcite

80-99-flink-tableapi-filnksql_第1张图片

使用样例

  • 导入pom依赖,1.11.X之后,推荐使用blink版本

    
    <dependency>
      <groupId>org.apache.flinkgroupId>
      <artifactId>flink-table-planner-blink_${scala.binary.version}artifactId>
      <version>${flink.version}version>
    dependency>
    
  • java样例代码

    src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest1_Example.java

  • Txt文件:sensor.txt

  • 输出结果

    result> sensor_1,35.8
    sql> sensor_1,35.8
    result> sensor_1,36.3
    sql> sensor_1,36.3
    result> sensor_1,32.8
    sql> sensor_1,32.8
    result> sensor_1,37.1
    sql> sensor_1,37.1
    

2 基本程序结构

  • Table API和SQL的程序结构,与流式处理的程序结构十分类似

    StreamTableEnvironment tableEnv = ... // 创建表的执行环境
    // 创建一张表,用于读取数据
    tableEnv.connect(...).createTemporaryTable("inputTable");
    // 注册一张表,用于把计算结果输出
    tableEnv.connect(...).createTemporaryTable("outputTable");
    // 通过 Table API 查询算子,得到一张结果表
    Table result = tableEnv.from("inputTable").select(...);
    // 通过SQL查询语句,得到一张结果表
    Table sqlResult = tableEnv.sqlQuery("SELECT ... FROM inputTable ...");
    // 将结果表写入输出表中
    result.insertInto("outputTable");
    

3 Table API批处理和流处理

新版本blink,真正把批处理、流处理都以DataStream实现。

创建环境-样例代码

src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest2_CommonApi.java

3.1 表(Table)

  • TableEnvironment可以注册目录Catalog,并可以基于Catalog注册表
  • 表(Table)是由一个"标示符"(identifier)来指定的,由3部分组成:Catalog名、数据库(database)名和对象名
  • 表可以是常规的,也可以是虚拟的(视图,View)
  • 常规表(Table)一般可以用来描述外部数据,比如文件、数据库表或消息队列的数据,也可以直接从DataStream转换而来
  • 视图(View)可以从现有的表中创建,通常是table API或者SQL查询的一个结果集

3.2 创建表

  • TableEnvironment可以调用connect()方法,连接外部系统,并调用.createTemporaryTable()方法,在Catalog中注册表

    tableEnv
      .connect(...)    //    定义表的数据来源,和外部系统建立连接
      .withFormat(...)    //    定义数据格式化方法
      .withSchema(...)    //    定义表结构
      .createTemporaryTable("MyTable");    //    创建临时表
    

3.3 创建TableEnvironment

  • 创建表的执行环境,需要将flink流处理的执行环境传入

    StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

  • TableEnvironment是flink中集成Table API和SQL的核心概念,所有对表的操作都基于TableEnvironment

    • 注册Catalog
    • 在Catalog中注册表
    • 执行SQL查询
    • 注册用户自定义函数(UDF)

测试代码

  • pom依赖

    
    <dependency>
      <groupId>org.apache.flinkgroupId>
      <artifactId>flink-table-planner-blink_${scala.binary.version}artifactId>
      <version>${flink.version}version>
    dependency>
    
    
    <dependency>
      <groupId>org.apache.flinkgroupId>
      <artifactId>flink-csvartifactId>
      <version>${flink.version}version>
    dependency>
    
  • java代码

    src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest2_CommonApi.java

  • 输入文件

    sensor.txt

  • 输出

    root
     |-- id: STRING
     |-- timestamp: BIGINT
     |-- temp: DOUBLE
     
    sensor_1,1547718199,35.8
    sensor_6,1547718201,15.4
    sensor_7,1547718202,6.7
    sensor_10,1547718205,38.1
    sensor_1,1547718207,36.3
    sensor_1,1547718209,32.8
    sensor_1,1547718212,37.1
    

3.4 表的查询

  • Table API是集成在Scala和Java语言内的查询API

  • Table API基于代表"表"的Table类,并提供一整套操作处理的方法API;这些方法会返回一个新的Table对象,表示对输入表应用转换操作的结果

  • 有些关系型转换操作,可以由多个方法调用组成,构成链式调用结构

    Table sensorTable = tableEnv.from("inputTable");
    Table resultTable = sensorTable
      .select("id","temperature")
      .filter("id = 'sensor_1'");
    

从文件获取数据

  • java代码

    src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest2_CommonApi.java

  • 输出结果

    里面的false表示上一条保存的记录被删除,true则是新加入的数据

    所以Flink的Table API在更新数据时,实际是先删除原本的数据,再添加新数据。

    result> sensor_6,15.4
    sqlagg> (true,sensor_1,1,35.8)
    sqlagg> (true,sensor_6,1,15.4)
    sqlagg> (true,sensor_7,1,6.7)
    sqlagg> (true,sensor_10,1,38.1)
    agg> (true,sensor_1,1,35.8)
    agg> (true,sensor_6,1,15.4)
    sqlagg> (false,sensor_1,1,35.8)
    sqlagg> (true,sensor_1,2,36.05)
    agg> (true,sensor_7,1,6.7)
    sqlagg> (false,sensor_1,2,36.05)
    sqlagg> (true,sensor_1,3,34.96666666666666)
    agg> (true,sensor_10,1,38.1)
    sqlagg> (false,sensor_1,3,34.96666666666666)
    sqlagg> (true,sensor_1,4,35.5)
    agg> (false,sensor_1,1,35.8)
    agg> (true,sensor_1,2,36.05)
    agg> (false,sensor_1,2,36.05)
    agg> (true,sensor_1,3,34.96666666666666)
    agg> (false,sensor_1,3,34.96666666666666)
    agg> (true,sensor_1,4,35.5)
    

数据写入到文件

flink Sql 1.11 executeSql报No operators defined in streaming topology. Cannot generate StreamGraph.:https://blog.csdn.net/qq_26502245/article/details/107376528

写入到文件有局限,只能是批处理,且只能是追加写,不能是更新式的随机写。

  • java代码

    src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest3_FileOutput.java

  • 输出结果(输出到out.txt文件)

    sensor_6,15.4
    
  • 这个程序只能运行一次,再运行一次报错

读写Kafka

Kafka作为消息队列,和文件系统类似的,只能往里追加数据,不能修改数据。

  • 测试代码

    src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest4_KafkaPipeLine.java

  • 启动kafka目录里自带的zookeeper

    $ bin/zookeeper-server-start.sh config/zookeeper.properties
    
  • 启动kafka服务

    $ bin/kafka-server-start.sh config/server.properties
    
  • 新建kafka生产者和消费者

    $ bin/kafka-console-producer.sh --broker-list localhost:9092  --topic sensor
    
    $ bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic sinktest
    
  • 启动Flink程序,在kafka生产者console中输入数据,查看输出

    • 输入(kafka-console-producer)

      >sensor_1,1547718199,35.8
      >sensor_6,1547718201,15.4
      >sensor_7,1547718202,6.7
      >sensor_10,1547718205,38.1
      >sensor_6,1547718209,34.5
      
    • 输出(kafka-console-consumer)

      代码中,只筛选id为sensor_6的,所以输出没有问题

      sensor_6,15.4           
      
      sensor_6,34.5
      

3.5 更新模式

  • 对于流式查询,需要声明如何在表和外部连接器之间执行转换
  • 与外部系统交换的消息类型,由更新模式(Uadate Mode)指定
  • 追加(Append)模式
    • 表只做插入操作,和外部连接器只交换插入(Insert)消息
  • 撤回(Retract)模式
    • 表和外部连接器交换添加(Add)和撤回(Retract)消息
    • 插入操作(Insert)编码为Add消息;删除(Delete)编码为Retract消息;更新(Update)编码为上一条的Retract和下一条的Add消息
  • 更新插入(Upsert)模式
    • 更新和插入都被编码为Upsert消息;删除编码为Delete消息

3.6 输出到ES

  • 可以创建Table来描述ES中的数据,作为输出的TableSink

    tableEnv.connect(
      new Elasticsearch()
      .version("6")
      .host("localhost",9200,"http")
      .index("sensor")
      .documentType("temp")
    )
      .inUpsertMode()
      .withFormat(new Json())
      .withSchema(new Schema()
                  .field("id",DataTypes.STRING())
                  .field("count",DataTypes.BIGINT())
                 )
      .createTemporaryTable("esOutputTable");
    aggResultTable.insertInto("esOutputTable");
    

3.7 输出到MySQL

  • 需要的pom依赖

    Flink专门为Table API的jdbc连接提供了flink-jdbc连接器,需要先引入依赖

    
    <dependency>
        <groupId>org.apache.flinkgroupId>
        <artifactId>flink-jdbc_2.12artifactId>
        <version>1.10.3version>
        <scope>providedscope>
    dependency>
    
  • 可以创建Table来描述MySql中的数据,作为输入和输出

    String sinkDDL = 
      "create table jdbcOutputTable (" +
      " id varchar(20) not null, " +
      " cnt bigint not null " +
      ") with (" +
      " 'connector.type' = 'jdbc', " +
      " 'connector.url' = 'jdbc:mysql://localhost:3306/test', " +
      " 'connector.table' = 'sensor_count', " +
      " 'connector.driver' = 'com.mysql.jdbc.Driver', " +
      " 'connector.username' = 'root', " +
      " 'connector.password' = '123456' )";
    tableEnv.sqlUpdate(sinkDDL);    // 执行DDL创建表
    aggResultSqlTable.insertInto("jdbcOutputTable");
    

4 表和流的转换

Flink- 将表转换成DataStream | 查看执行计划 | 流处理和关系代数的区别 | 动态表 | 流式持续查询的过程 | 将流转换成动态表 | 持续查询 | 将动态表转换成 DS:https://blog.csdn.net/qq_40180229/article/details/106479537

4.1 将Table转换成DataStream

  • 表可以转换为 DataStream 或 DataSet ,这样自定义流处理或批处理程序就可以继续在 Table API 或 SQL 查询的结果上运行了

  • 将表转换为 DataStream 或 DataSet 时,需要指定生成的数据类型,即要将表的每一行转换成的数据类型

  • 表作为流式查询的结果,是动态更新的

  • 转换有两种转换模式:追加(Appende)模式和撤回(Retract)模式

  • 追加模式

    • 用于表只会被插入(Insert)操作更改的场景
    DataStream<Row> resultStream = tableEnv.toAppendStream(resultTable,Row.class);
    
  • 撤回模式

    • 用于任何场景。有些类似于更新模式中Retract模式,它只有Insert和Delete两类操作。

    • 得到的数据会增加一个Boolean类型的标识位(返回的第一个字段),用它来表示到底是新增的数据(Insert),还是被删除的数据(Delete)

      (更新数据,会先删除旧数据,再插入新数据)

4.2 将DataStream转换成表

  • 对于一个DataStream,可以直接转换成Table,进而方便地调用Table API做转换操作

    DataStream<SensorReading> dataStream = ...
    Table sensorTable = tableEnv.fromDataStream(dataStream);
    
  • 默认转换后的Table schema和DataStream中的字段定义一一对应,也可以单独指定出来

    DataStream<SensorReading> dataStream = ...
    Table sensorTable = tableEnv.fromDataStream(dataStream,
                                               "id, timestamp as ts, temperature");
    

4.3 创建临时视图(Temporary View)

  • 基于DataStream创建临时视图

    tableEnv.createTemporaryView("sensorView",dataStream);
    tableEnv.createTemporaryView("sensorView",
                                dataStream, "id, timestamp as ts, temperature");
    
  • 基于Table创建临时视图

    tableEnv.createTemporaryView("sensorView", sensorTable);
    

5 查看执行计划

  • Table API 提供了一种机制来解释计算表的逻辑和优化查询计划

  • 查看执行计划,可以通过TableEnvironment.explain(table)方法或TableEnvironment.explain()方法完成,返回一个字符串,描述三个计划

    • 优化的逻辑查询计划
    • 优化后的逻辑查询计划
    • 实际执行计划
    String explaination = tableEnv.explain(resultTable);
    System.out.println(explaination);
    

6 流处理和关系代数的区别

Flink- 将表转换成DataStream | 查看执行计划 | 流处理和关系代数的区别 | 动态表 | 流式持续查询的过程 | 将流转换成动态表 | 持续查询 | 将动态表转换成 DS :https://blog.csdn.net/qq_40180229/article/details/106479537

Table API和SQL,本质上还是基于关系型表的操作方式;而关系型表、关系代数,以及SQL本身,一般是有界的,更适合批处理的场景。这就导致在进行流处理的过程中,理解会稍微复杂一些,需要引入一些特殊概念。

其实关系代数(主要就是指关系型数据库中的表)和SQL,主要就是针对批处理的,这和流处理有天生的隔阂。

关系代数(表)/SQL 流处理
处理的数据对象 字段元组的有界集合 字段元组的无限序列
查询(Query)对数据的访问 可以访问到完整的数据输入 无法访问所有数据,必须持续"等待"流式输入
查询终止条件 生成固定大小的结果集后终止 永不停止,根据持续收到的数据不断更新查询结果

6.1 动态表(Dynamic Tables)

随着新数据的到来,不停地在之前的基础上更新结果。这样得到的表,在Flink Table API概念里,就叫做“动态表”(Dynamic Tables)。

  • 动态表是 Flink 对流数据的 Table API 和 SQL 支持的核心概念
  • 与表示批处理数据的静态表不同,动态表是随时间变化的
  • 持续查询(Continuous Query)
    • 动态表可以像静态的批处理表一样进行查询,查询一个动态表会产生持续查询(Continuous Query)
    • 连续查询永远不会终止,并会生成另一个动态表
    • 查询(Query)会不断更新其动态结果表,以反映其动态输入表上的更改。

6.2 动态表和持续查询

80-99-flink-tableapi-filnksql_第2张图片

流式表查询的处理过程:

  1. 流被转换为动态表
  2. 对动态表计算连续查询,生成新的动态表
  3. 生成的动态表被转换回流

6.3 将流转换成动态表

  • 为了处理带有关系查询的流,必须先将其转换为表
  • 从概念上讲,流的每个数据记录,都被解释为对结果表的插入(Insert)修改操作本质上,我们其实是从一个、只有插入操作的changelog(更新日志)流,来构建一个表

来一条数据插入一条数据

80-99-flink-tableapi-filnksql_第3张图片

6.4 持续查询

  • 持续查询,会在动态表上做计算处理,并作为结果生成新的动态表。

    与批处理查询不同,连续查询从不终止,并根据输入表上的更新更新其结果表。

    在任何时间点,连续查询的结果在语义上,等同于在输入表的快照上,以批处理模式执行的同一查询的结果。

下图为一个点击事件流的持续查询,是一个分组聚合做count统计的查询。

80-99-flink-tableapi-filnksql_第4张图片

6.4 将动态表转换成 DataStream

  • 与常规的数据库表一样,动态表可以通过插入(Insert)、更新(Update)和删除(Delete)更改,进行持续的修改
  • 将动态表转换为流或将其写入外部系统时,需要对这些更改进行编码

  • 仅追加(Append-only)流

    • 仅通过插入(Insert)更改来修改的动态表,可以直接转换为仅追加流
  • 撤回(Retract)流

    • 撤回流是包含两类消息的流:添加(Add)消息和撤回(Retract)消息

      动态表通过将INSERT 编码为add消息、DELETE 编码为retract消息、UPDATE编码为被更改行(前一行)的retract消息和更新后行(新行)的add消息,转换为retract流。

  • Upsert(更新插入流)

    • Upsert流也包含两种类型的消息:Upsert消息和删除(Delete)消息

      通过将INSERT和UPDATE更改编码为upsert消息,将DELETE更改编码为DELETE消息,就可以将具有唯一键(Unique Key)的动态表转换为流。

6.5 将动态表转换成DataStream

80-99-flink-tableapi-filnksql_第5张图片

时间特性(Time Attributes)

1 概述

  • 基于时间的操作(比如 Table API 和 SQL 中窗口操作),需要定义相关的时间语义和时间数据来源的信息
  • Table 可以提供一个逻辑上的时间字段,用于在表处理程序中,指示时间和访问相应的时间戳
  • 时间属性,可以是每个表schema的一部分。一旦定义了时间属性,它就可以作为一个字段引用,并且可以在基于时间的操作中使用
  • 时间属性的行为类似于常规时间戳,可以访问,并且进行计算

2 定义处理时间(Processing Time)

  • 处理时间语义下,允许表处理程序根据机器的本地时间生成结果。它是时间的最简单概念。它既不需要提取时间戳,也不需要生成 watermark

由DataStream转换成表时指定

  • 在定义 Table Schema 期间,可以使用.proctime,指定字段名定义处理时间字段

  • 这个proctime属性只能通过附加逻辑字段,来扩展物理schema。因此,只能在schema定义的末尾定义它

    Table sensorTable = tableEnv.fromDataStream(dataStream,"id, temperature, pt.proctime");
    

定义Table Schema时指定

.withSchema(new Schema()
            .field("id", DataTypes.STRING())
            .field("timestamp",DataTypes.BIGINT())
            .field("temperature",DataTypes.DOUBLE())
            .field("pt",DataTypes.TIMESTAMP(3))
            .proctime()
           )

创建表的DDL中定义

String sinkDDL = 
  "create table dataTable (" +
  " id varchar(20) not null, " +
  " ts bigint, " +
  " temperature double, " +
  " pt AS PROCTIME() " +
  " ) with (" +
  " 'connector.type' = 'filesystem', " +
  " 'connector.path' = '/sensor.txt', " +
  " 'format.type' = 'csv')";
tableEnv.sqlUpdate(sinkDDL);

测试代码

src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest5_TimeAndWindow.java

输出如下:

root
 |-- id: STRING
 |-- ts: BIGINT
 |-- temp: DOUBLE
 |-- pt: TIMESTAMP(3) *PROCTIME*

sensor_1,1547718199,35.8,2021-02-03T16:50:58.048
sensor_6,1547718201,15.4,2021-02-03T16:50:58.048
sensor_7,1547718202,6.7,2021-02-03T16:50:58.050
sensor_10,1547718205,38.1,2021-02-03T16:50:58.050
sensor_1,1547718207,36.3,2021-02-03T16:50:58.051
sensor_1,1547718209,32.8,2021-02-03T16:50:58.051
sensor_1,1547718212,37.1,2021-02-03T16:50:58.051

3 定义事件事件(Event Time)

  • 事件时间语义,允许表处理程序根据每个记录中包含的时间生成结果。这样即使在有乱序事件或者延迟事件时,也可以获得正确的结果。
  • 为了处理无序事件,并区分流中的准时和迟到事件;Flink需要从事件数据中,提取时间戳,并用来推送事件时间的进展
  • 定义事件事件,同样有三种方法:
    • 由DataStream转换成表时指定
    • 定义Table Schema时指定
    • 在创建表的DDL中定义

由DataStream转换成表时指定

  • 由DataStream转换成表时指定(推荐)

  • 在DataStream转换成Table,使用.rowtime可以定义事件事件属性

    // 将DataStream转换为Table,并指定时间字段
    Table sensorTable = tableEnv.fromDataStream(dataStream,
                                               "id, timestamp.rowtime, temperature");
    // 或者,直接追加时间字段
    Table sensorTable = tableEnv.fromDataStream(dataStream,
                                              "id, temperature, timestamp, rt.rowtime");
    

定义Table Schema时指定

.withSchema(new Schema()
            .field("id", DataTypes.STRING())
            .field("timestamp",DataTypes.BIGINT())
            .rowtime(
              new Rowtime()
              .timestampsFromField("timestamp") // 从字段中提取时间戳
              .watermarksPeriodicBounded(1000) // watermark延迟1秒
            )
            .field("temperature",DataTypes.DOUBLE())
           )

创建表的DDL中定义

String sinkDDL = 
  "create table dataTable (" +
  " id varchar(20) not null, " +
  " ts bigint, " +
  " temperature double, " +
  " rt AS TO_TIMESTAMP( FROM_UNIXTIME(ts) ), " +
  " watermark for rt as rt - interval '1' second"
  " ) with (" +
  " 'connector.type' = 'filesystem', " +
  " 'connector.path' = '/sensor.txt', " +
  " 'format.type' = 'csv')";
tableEnv.sqlUpdate(sinkDDL);

测试代码

src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest5_TimeAndWindow.java

输出如下:

注:这里最后一列rt里显示的是EventTime,而不是Processing Time

root
 |-- id: STRING
 |-- ts: BIGINT
 |-- temp: DOUBLE
 |-- rt: TIMESTAMP(3) *ROWTIME*

sensor_1,1547718199,35.8,2019-01-17T09:43:19
sensor_6,1547718201,15.4,2019-01-17T09:43:21
sensor_7,1547718202,6.7,2019-01-17T09:43:22
sensor_10,1547718205,38.1,2019-01-17T09:43:25
sensor_1,1547718207,36.3,2019-01-17T09:43:27
sensor_1,1547718209,32.8,2019-01-17T09:43:29
sensor_1,1547718212,37.1,2019-01-17T09:43:32

4 窗口

Flink-分组窗口 | Over Windows | SQL 中的 Group Windows | SQL 中的 Over Windows:https://blog.csdn.net/qq_40180229/article/details/106482095

  • 时间语义,要配合窗口操作才能发挥作用。
  • 在Table API和SQL中,主要有两种窗口
    • Group Windows(分组窗口)
      • 根据时间戳或行计数间隔,将行聚合到有限的组(Group)中,并对每个组的数据执行一次聚合函数
    • Over Windows
      • 针对每个输入行,计算相邻行范围内的聚合

4.1 Group Windows

  • Group Windows 是使用 window(w:GroupWindow)子句定义的,并且必须由as子句指定一个别名

  • 为了按窗口对表进行分组,窗口的别名必须在 group by 子句中,像常规的分组字段一样引用

    Table table = input
    .window([w:GroupWindow] as "w") // 定义窗口,别名为w
    .groupBy("w, a") // 按照字段 a和窗口 w分组
    .select("a,b.sum"); // 聚合
    
  • Table API 提供了一组具有特定语义的预定义 Window 类,这些类会被转换为底层 DataStream 或 DataSet 的窗口操作

  • 分组窗口分为三种:

    • 滚动窗口
    • 滑动窗口
    • 会话窗口

滚动窗口(Tumbling windows)

  • 滚动窗口(Tumbling windows)要用Tumble类来定义
// Tumbling Event-time Window(事件时间字段rowtime)
.window(Tumble.over("10.minutes").on("rowtime").as("w"))

// Tumbling Processing-time Window(处理时间字段proctime)
.window(Tumble.over("10.minutes").on("proctime").as("w"))

// Tumbling Row-count Window (类似于计数窗口,按处理时间排序,10行一组)
.window(Tumble.over("10.rows").on("proctime").as("w"))
  • over:定义窗口长度
  • on:用来分组(按时间间隔)或者排序(按行数)的时间字段
  • as:别名,必须出现在后面的groupBy中

滑动窗口(Sliding windows)

  • 滑动窗口(Sliding windows)要用Slide类来定义
// Sliding Event-time Window
.window(Slide.over("10.minutes").every("5.minutes").on("rowtime").as("w"))

// Sliding Processing-time window 
.window(Slide.over("10.minutes").every("5.minutes").on("proctime").as("w"))

// Sliding Row-count window
.window(Slide.over("10.rows").every("5.rows").on("proctime").as("w"))
  • over:定义窗口长度
  • every:定义滑动步长
  • on:用来分组(按时间间隔)或者排序(按行数)的时间字段
  • as:别名,必须出现在后面的groupBy中

会话窗口(Session windows)

  • 会话窗口(Session windows)要用Session类来定义
// Session Event-time Window
.window(Session.withGap("10.minutes").on("rowtime").as("w"))

// Session Processing-time Window 
.window(Session.withGap("10.minutes").on("proctime").as("w"))
  • withGap:会话时间间隔
  • on:用来分组(按时间间隔)或者排序(按行数)的时间字段
  • as:别名,必须出现在后面的groupBy中

4.2 SQL中的Group Windows

Group Windows定义在SQL查询的Group By子句中

  • TUMBLE(time_attr, interval)
    • 定义一个滚动窗口,每一个参数是时间字段,第二个参数是窗口长度
  • HOP(time_attr,interval,interval)
    • 定义一个滑动窗口,第一个参数是时间字段,第二个参数是窗口滑动步长,第三个是窗口长度
  • SESSION(time_attr,interval)
    • 定义一个绘画窗口,第一个参数是时间字段,第二个参数是窗口间隔

测试代码

src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest5_TimeAndWindow.java

输出:

root
 |-- id: STRING
 |-- ts: BIGINT
 |-- temp: DOUBLE
 |-- rt: TIMESTAMP(3) *ROWTIME*

result> sensor_1,1,35.8,2019-01-17T09:43:20
result> sensor_6,1,15.4,2019-01-17T09:43:30
result> sensor_1,2,34.55,2019-01-17T09:43:30
result> sensor_10,1,38.1,2019-01-17T09:43:30
result> sensor_7,1,6.7,2019-01-17T09:43:30
sql> (true,sensor_1,1,35.8,2019-01-17T09:43:20)
result> sensor_1,1,37.1,2019-01-17T09:43:40
sql> (true,sensor_6,1,15.4,2019-01-17T09:43:30)
sql> (true,sensor_1,2,34.55,2019-01-17T09:43:30)
sql> (true,sensor_10,1,38.1,2019-01-17T09:43:30)
sql> (true,sensor_7,1,6.7,2019-01-17T09:43:30)
sql> (true,sensor_1,1,37.1,2019-01-17T09:43:40)

4.3 Over Windows

SQL中over的用法:https://blog.csdn.net/liuyuehui110/article/details/42736667

sql over的作用及用法:https://www.cnblogs.com/xiayang/articles/1886372.html

  • Over window 聚合是标准 SQL 中已有的(over 子句),可以在查询的 SELECT 子句中定义

  • Over window 聚合,会针对每个输入行,计算相邻行范围内的聚合

  • Over windows 使用 window(w:overwindows*)子句定义,并在 select()方法中通过别名来引用

    Table table = input
    .window([w: OverWindow] as "w")
    .select("a, b.sum over w, c.min over w");
    
  • Table API 提供了 Over 类,来配置 Over 窗口的属性

无界Over Windows

  • 可以在事件时间或处理时间,以及指定为时间间隔、或行计数的范围内,定义 Over windows
  • 无界的 over window 是使用常量指定的
// 无界的事件时间over window (时间字段 "rowtime")
.window(Over.partitionBy("a").orderBy("rowtime").preceding(UNBOUNDED_RANGE).as("w"))

//无界的处理时间over window (时间字段"proctime")
.window(Over.partitionBy("a").orderBy("proctime").preceding(UNBOUNDED_RANGE).as("w"))

// 无界的事件时间Row-count over window (时间字段 "rowtime")
.window(Over.partitionBy("a").orderBy("rowtime").preceding(UNBOUNDED_ROW).as("w"))

//无界的处理时间Row-count over window (时间字段 "rowtime")
.window(Over.partitionBy("a").orderBy("proctime").preceding(UNBOUNDED_ROW).as("w"))

partitionBy是可选项

有界Over Windows

  • 有界的over window是用间隔的大小指定的
// 有界的事件时间over window (时间字段 "rowtime",之前1分钟)
.window(Over.partitionBy("a").orderBy("rowtime").preceding("1.minutes").as("w"))

// 有界的处理时间over window (时间字段 "rowtime",之前1分钟)
.window(Over.partitionBy("a").orderBy("porctime").preceding("1.minutes").as("w"))

// 有界的事件时间Row-count over window (时间字段 "rowtime",之前10行)
.window(Over.partitionBy("a").orderBy("rowtime").preceding("10.rows").as("w"))

// 有界的处理时间Row-count over window (时间字段 "rowtime",之前10行)
.window(Over.partitionBy("a").orderBy("proctime").preceding("10.rows").as("w"))

4.4 SQL中的Over Windows

  • 用 Over 做窗口聚合时,所有聚合必须在同一窗口上定义,也就是说必须是相同的分区、排序和范围
  • 目前仅支持在当前行范围之前的窗口
  • ORDER BY 必须在单一的时间属性上指定
SELECT COUNT(amount) OVER (
  PARTITION BY user
  ORDER BY proctime
  ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
FROM Orders

// 也可以做多个聚合
SELECT COUNT(amount) OVER w, SUM(amount) OVER w
FROM Orders
WINDOW w AS (
  PARTITION BY user
  ORDER BY proctime
  ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)

测试代码

java代码

src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest5_TimeAndWindow.java

输出:

因为partition by id order by rt rows between 2 preceding and current row,所以最后2次关于sensor_1的输出的count(id)都是3,但是计算出来的平均值不一样。(前者计算倒数3条sensor_1的数据,后者计算最后最新的3条sensor_1数据的平均值)

result> sensor_1,2019-01-17T09:43:19,1,35.8
sql> (true,sensor_1,2019-01-17T09:43:19,1,35.8)
result> sensor_6,2019-01-17T09:43:21,1,15.4
sql> (true,sensor_6,2019-01-17T09:43:21,1,15.4)
result> sensor_7,2019-01-17T09:43:22,1,6.7
sql> (true,sensor_7,2019-01-17T09:43:22,1,6.7)
result> sensor_10,2019-01-17T09:43:25,1,38.1
sql> (true,sensor_10,2019-01-17T09:43:25,1,38.1)
result> sensor_1,2019-01-17T09:43:27,2,36.05
sql> (true,sensor_1,2019-01-17T09:43:27,2,36.05)
sql> (true,sensor_1,2019-01-17T09:43:29,3,34.96666666666666)
result> sensor_1,2019-01-17T09:43:29,3,34.96666666666666
result> sensor_1,2019-01-17T09:43:32,3,35.4
sql> (true,sensor_1,2019-01-17T09:43:32,3,35.4)

函数(Functions)

Flink-函数 | 用户自定义函数(UDF)标量函数 | 表函数 | 聚合函数 | 表聚合函数:https://blog.csdn.net/qq_40180229/article/details/106482550

80-99-flink-tableapi-filnksql_第6张图片80-99-flink-tableapi-filnksql_第7张图片

1 用户自定义函数(UDF)

  • 用户定义函数(User-defined Functions,UDF)是一个重要的特性,它们显著地扩展了查询的表达能力

    一些系统内置函数无法解决的需求,我们可以用UDF来自定义实现

  • 在大多数情况下,用户定义的函数必须先注册,然后才能在查询中使用

  • 函数通过调用 registerFunction() 方法在 TableEnvironment 中注册。当用户定义的函数被注册时,它被插入到 TableEnvironment 的函数目录中,这样Table API 或 SQL 解析器就可以识别并正确地解释它

1.1 标量函数(Scalar Functions)

Scalar Funcion类似于map,一对一

Table Function类似flatMap,一对多


  • 用户定义的标量函数,可以将0、1或多个标量值,映射到新的标量值

  • 为了定义标量函数,必须在 org.apache.flink.table.functions 中扩展基类Scalar Function,并实现(一个或多个)求值(eval)方法

  • 标量函数的行为由求值方法决定,求值方法必须public公开声明并命名为 eval

    public static class HashCode extends ScalarFunction {
      private int factor = 13;
    
      public HashCode(int factor) {
        this.factor = factor;
      }
    
      public int eval(String id) {
        return id.hashCode() * 13;
      }
    }
    

测试代码

src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/udf/UdfTest1_ScalarFunction.java

输出结果

sensor_1,1547718199,-772373508
sensor_1,1547718199,-772373508
sensor_6,1547718201,-772373443
sensor_6,1547718201,-772373443
sensor_7,1547718202,-772373430
sensor_7,1547718202,-772373430
sensor_10,1547718205,1826225652
sensor_10,1547718205,1826225652
sensor_1,1547718207,-772373508
sensor_1,1547718207,-772373508
sensor_1,1547718209,-772373508
sensor_1,1547718209,-772373508
sensor_1,1547718212,-772373508
sensor_1,1547718212,-772373508

1.2 表函数(Table Fcuntions)

Scalar Funcion类似于map,一对一

Table Function类似flatMap,一对多


  • 用户定义的表函数,也可以将0、1或多个标量值作为输入参数;与标量函数不同的是,它可以返回任意数量的行作为输出,而不是单个值

  • 为了定义一个表函数,必须扩展 org.apache.flink.table.functions 中的基类 TableFunction 并实现(一个或多个)求值方法

  • 表函数的行为由其求值方法决定,求值方法必须是 public 的,并命名为 eval

    public static class Split extends TableFunction<Tuple2<String, Integer>> {
    
      // 定义属性,分隔符
      private String separator = ",";
    
      public Split(String separator) {
        this.separator = separator;
      }
    
      public void eval(String str) {
        for (String s : str.split(separator)) {
          collect(new Tuple2<>(s, s.length()));
        }
      }
    }
    

测试代码

src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/udf/UdfTest2_TableFunction.java

输出结果

result> sensor_1,1547718199,sensor,6
result> sensor_1,1547718199,1,1
sql> sensor_1,1547718199,sensor,6
sql> sensor_1,1547718199,1,1
result> sensor_6,1547718201,sensor,6
result> sensor_6,1547718201,6,1
sql> sensor_6,1547718201,sensor,6
sql> sensor_6,1547718201,6,1
result> sensor_7,1547718202,sensor,6
result> sensor_7,1547718202,7,1
sql> sensor_7,1547718202,sensor,6
sql> sensor_7,1547718202,7,1
result> sensor_10,1547718205,sensor,6
result> sensor_10,1547718205,10,2
sql> sensor_10,1547718205,sensor,6
sql> sensor_10,1547718205,10,2
result> sensor_1,1547718207,sensor,6
result> sensor_1,1547718207,1,1
sql> sensor_1,1547718207,sensor,6
sql> sensor_1,1547718207,1,1
result> sensor_1,1547718209,sensor,6
result> sensor_1,1547718209,1,1
sql> sensor_1,1547718209,sensor,6
sql> sensor_1,1547718209,1,1
result> sensor_1,1547718212,sensor,6
result> sensor_1,1547718212,1,1
sql> sensor_1,1547718212,sensor,6
sql> sensor_1,1547718212,1,1

1.3 聚合函数(Aggregate Functions)

聚合,多对一,类似前面的窗口聚合


  • 用户自定义聚合函数(User-Defined Aggregate Functions,UDAGGs)可以把一个表中的数据,聚合成一个标量值
  • 用户定义的聚合函数,是通过继承 AggregateFunction 抽象类实现的

80-99-flink-tableapi-filnksql_第8张图片

  • AggregationFunction要求必须实现的方法
    • createAccumulator()
    • accumulate()
    • getValue()
  • AggregateFunction 的工作原理如下:
    • 首先,它需要一个累加器(Accumulator),用来保存聚合中间结果的数据结构;可以通过调用 createAccumulator() 方法创建空累加器
    • 随后,对每个输入行调用函数的 accumulate() 方法来更新累加器
    • 处理完所有行后,将调用函数的 getValue() 方法来计算并返回最终结果

测试代码

src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/udf/UdfTest3_AggregateFunction.java

输出结果:

result> (true,sensor_1,35.8)
result> (true,sensor_6,15.4)
result> (true,sensor_7,6.7)
result> (true,sensor_10,38.1)
result> (false,sensor_1,35.8)
result> (true,sensor_1,36.05)
sql> (true,sensor_1,35.8)
result> (false,sensor_1,36.05)
sql> (true,sensor_6,15.4)
result> (true,sensor_1,34.96666666666666)
sql> (true,sensor_7,6.7)
result> (false,sensor_1,34.96666666666666)
sql> (true,sensor_10,38.1)
result> (true,sensor_1,35.5)
sql> (false,sensor_1,35.8)
sql> (true,sensor_1,36.05)
sql> (false,sensor_1,36.05)
sql> (true,sensor_1,34.96666666666666)
sql> (false,sensor_1,34.96666666666666)
sql> (true,sensor_1,35.5)

1.4 表聚合函数

  • 用户定义的表聚合函数(User-Defined Table Aggregate Functions,UDTAGGs),可以把一个表中数据,聚合为具有多行和多列的结果表
  • 用户定义表聚合函数,是通过继承 TableAggregateFunction 抽象类来实现的

80-99-flink-tableapi-filnksql_第9张图片

  • AggregationFunction 要求必须实现的方法:
    • createAccumulator()
    • accumulate()
    • emitValue()
  • TableAggregateFunction 的工作原理如下:
    • 首先,它同样需要一个累加器(Accumulator),它是保存聚合中间结果的数据结构。通过调用 createAccumulator() 方法可以创建空累加器。
    • 随后,对每个输入行调用函数的 accumulate() 方法来更新累加器。
    • 处理完所有行后,将调用函数的 emitValue() 方法来计算并返回最终结果。

[测试代码]

Flink-函数 | 用户自定义函数(UDF)标量函数 | 表函数 | 聚合函数 | 表聚合函数:https://blog.csdn.net/qq_40180229/article/details/106482550

主来源::https://space.bilibili.com/302417610?spm_id_from=333.337.0.0
参考:https://ashiamd.github.io/docsify-notes/#/study/BigData/Flink/%E5%B0%9A%E7%A1%85%E8%B0%B7Flink%E5%85%A5%E9%97%A8%E5%88%B0%E5%AE%9E%E6%88%98-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0

你可能感兴趣的:(大数据,flink,java,flink,java,大数据)