10 Flink Table API 和 SQL

10 Flink Table API 和 SQL

Table API 是流处理和批处理通用的关系型 API,Table API 可以基于流输入或者批输入来运行而不需要进行任何修改。Table API 是 SQL 语言的超集并专门为 Apache Flink 设计的,Table API 是 Scala 和 Java 语言集成式的 API。与常规 SQL 语言中将查询指定为字符串不同,Table API 查询是以 Java 或 Scala 中的语言嵌入样式来定义的,具有 IDE 支持如:自动完成和语法检测。

package table;

import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import org.apache.flink.types.Row;
import pojo.SensorReading;

/**
 * @author wangkai
 *
 */
public class Example1 {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        env.setParallelism(1);
        /**
         *
         * 1 读取数据
         * */

        DataStreamSource<String> source = env.readTextFile("file path");
        /**
         * 2 转换成pojo
         * */
        DataStream<SensorReading> dataStream = source.map(line -> {
            String[] fields = line.split(",");
            return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
        });
        /**
         * 3 创建表环境
         * */

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
        /**
         * 4 基于流创建一张表
         * */
        Table dataTable = tableEnv.fromDataStream(dataStream);
        /**
         * 5 调用table api进行转换操作
         *
         * */
        Table resultTable = dataTable.select("id,temperature").where("id = 'sensor_1'");
        /**
         * 6 执行sql
         * */
        tableEnv.createTemporaryView("sensor",dataStream);
        String sql = "select id,temperature from sensor where id='sensor_1'";
        Table sqlResult = tableEnv.sqlQuery(sql);

        tableEnv.toAppendStream(resultTable, Row.class).print("table result");
        tableEnv.toAppendStream(sqlResult,Row.class).print("sql result");

        env.execute("table");

    }
}

由上述程序可知,创建一个完整的Table api程序流程如下:

  • 创建tableEnviroment

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

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
      
    • TableEnvironment 是 flink 中集成 Table API 和 SQL 的核心概念,所有对表的操作都基于 TableEnvironment

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

    package table;
    
    import org.apache.flink.api.java.ExecutionEnvironment;
    import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
    import org.apache.flink.table.api.EnvironmentSettings;
    import org.apache.flink.table.api.TableEnvironment;
    import org.apache.flink.table.api.bridge.java.BatchTableEnvironment;
    import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
    
    /**
     * @author wangkai 
     * 
     */
    public class Example2 {
        public static void main(String[] args) {
            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    
            /**
             *配置老版本 planner 的流式查询
             * */
            EnvironmentSettings oldSettings = EnvironmentSettings.newInstance()
                    .useOldPlanner()
                    .inStreamingMode()
                    .build();
    
            StreamTableEnvironment oldTableEnviroment = StreamTableEnvironment.create(env, oldSettings);
    
            /**
             * 配置老版本批处理模式
             * */
            ExecutionEnvironment bathEnv = ExecutionEnvironment.getExecutionEnvironment();
            BatchTableEnvironment batchTableEnvironment = BatchTableEnvironment.create(bathEnv);
    
    
            /**
             * 配置新版本planner的流式查询
             * */
            EnvironmentSettings newSettings = EnvironmentSettings
                    .newInstance()
                    .useBlinkPlanner()
                    .inStreamingMode()
                    .build();
            StreamTableEnvironment newTableEnv = StreamTableEnvironment.create(env, newSettings);
    
    
            /**
             * 配置新版本的批查询
             * */
    
            EnvironmentSettings batchSettings = EnvironmentSettings
                    .newInstance()
                    .inBatchMode()
                    .inBatchMode()
                    .build();
            TableEnvironment batchEnv = TableEnvironment.create(batchSettings);
    
    
        }
    }
    
    

    TableEnvironment 可以注册目录 Catalog,并可以基于 Catalog 注册表,表(Table)是由一个“标识符”(identifier)来指定的,由3部分组成:

    Catalog名、数据库(database)名和对象名 ,表可以是常规的,也可以是虚拟的(视图,View) 。常规表(Table)一般可以用来描述外部数据,比如文件、数据库表或消息队列的数据,也可以直接从 DataStream转换而来,视图(View)可以从现有的表中创建,通常是 table API 或者 SQL 查询的一个结果集。TableEnvironment 可以调用 .connect() 方法,连接外部系统,并调用 .createTemporaryTable() 方法,在 Catalog 中注册表

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

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.DataTypes;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import org.apache.flink.table.descriptors.Csv;
import org.apache.flink.table.descriptors.FileSystem;
import org.apache.flink.table.descriptors.Schema;
import org.apache.flink.types.Row;

/**
 * @author wangkai
 *
 */
public class Example3 {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        env.setParallelism(1);

        EnvironmentSettings newSettings = EnvironmentSettings
                .newInstance()
                .useBlinkPlanner()
                .inStreamingMode()
                .build();
        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env, newSettings);

        /**
         * 创建表,连接外部系统,读取表
         * */
        String path = "XXX";

        tableEnv
                .connect(new FileSystem().path(path))
                .withFormat(new Csv())
                .withSchema(new Schema()
                        .field("id", DataTypes.STRING())
                        .field("timestamp",DataTypes.BIGINT())
                        .field("temperature",DataTypes.DOUBLE())
                ).createTemporaryTable("inputTable");

        Table inputTable = tableEnv.from("inputTable");

        Table resultTable = inputTable.select("id,temperature").filter("id === 'sensor_6'");

        // 聚合统计
        Table aggTable = inputTable.groupBy("id")
                .select("id, id.count as count, temperature.avg as avgtemperature");

        // 3.2 SQL
        tableEnv.sqlQuery("select id, temperature from inputTable where id = 'senosr_6'");
        Table sqlAggTable = tableEnv.sqlQuery("select id, count(id) as cnt, avg(temperature) as avgtemperature from inputTable group by id");

        // 打印输出
        tableEnv.toAppendStream(resultTable, Row.class).print("result");
        tableEnv.toRetractStream(aggTable, Row.class).print("agg");
        tableEnv.toRetractStream(sqlAggTable, Row.class).print("sqlagg");

        env.execute();
    }
}

从上述程序可以看出,Table api的建表和查询流程如下:

  • 创建表

            EnvironmentSettings settings = EnvironmentSettings
                    .newInstance()
                    .useBlinkPlanner()
                    .inStreamingMode()
                    .build();
    
            StreamTableEnvironment streamTableEnvironment = StreamTableEnvironment.create(env, settings);
    
            streamTableEnvironment.connect(new FileSystem().path(path))
                    .withFormat(new Csv())
                    .withSchema(new Schema()
                            .field("id", DataTypes.STRING())
                            .field("time",DataTypes.BIGINT())
                            .field("temp",DataTypes.DOUBLE())
                    ).createTemporaryTable("sensor");
    
            Table sensor = streamTableEnvironment.from("sensor");
    
    
                                /**
                                result:
                                root
                                 |-- id: STRING
                                 |-- time: BIGINT
                                 |-- temp: DOUBLE
                                */
    
  • 查询表

    • Table API

      Table API 是集成在 Scala 和 Java 语言内的查询 API,Table API 基于代表“表”的 Table 类,并提供一整套操作处理的方法 API;这些方法会返回一个新的 Table 对象,表示对输入表应用转换操作的结果。有些关系型转换操作,可以由多个方法调用组成,构成链式调用结构。

          Table sensorTable = streamTableEnvironment.from("sensor");       
          Table filterTable = sensorTable.select("id,temp").filter("id === 6");
      
    • SQL

    ​ Flink 的 SQL 集成,基于实现 了SQL 标准的 Apache Calcite,在 Flink 中,用常规字符串来定义 SQL 查询语句。SQL 查询的结果,也是一个新的 Table

Table sqlQueryTable = streamTableEnvironment.sqlQuery("select id,temp from sensor where id = 'sensor_1'");
  • 输出表

    表的输出,是通过将数据写入 TableSink 来实现的,TableSink 是一个通用接口,可以支持不同的文件格式、存储数据库和消息队列,输出表最直接的方法,就是通过 Table.insertInto() 方法将一个 Table 写入注册

tableEnv.connect(...)
.createTemporaryTable("outputTable");
Table resultSqlTable = ...
resultTable.insertInto("outputTable");
  • 更新模式

    对于流式查询,需要声明如何在表和外部连接器之间执行转换,与外部系统交换的消息类型,由更新模式(Update Mode)指定

    • 追加(Append)模式

      表只做插入操作,和外部连接器只交换插入(Insert)消息

    • 撤回(Retract)模式

      表和外部连接器交换添加(Add)和撤回(Retract)消息,插入操作(Insert)编码为 Add 消息;删除(Delete)编码为 Retract 消息;更新(Update)编码为上一条的 Retract 和下一条的 Add 消息

    • 更新插入(Upsert)模式

      更新和插入都被编码为 Upsert 消息;删除编码为 Delete 消息

/**输出到kafka*/
tableEnv.connect(
new Kafka()
.version("0.11")
.topic("sinkTest")
.property("zookeeper.connect", "localhost:2181")
.property("bootstrap.servers", "localhost:9092") )
.withFormat( new Csv() )
.withSchema( new Schema()
.field("id", DataTypes.STRING())
.field("temp", DataTypes.DOUBLE())
)
.createTemporaryTable("kafkaOutputTable");
resultTable.insertInto("kafkaOutputTable");
/**可以创建 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");
可以创建 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");
  • 将 Table 转换成 DataStream

    表可以转换为 DataStream 或 DataSet ,这样自定义流处理或批处理程序就可以继续在 Table API 或 SQL 查询的结果上运行了。将表转换为 DataStream 或 DataSet 时,需要指定生成的数据类型,即要将表的每一行转换成的数据类型。表作为流式查询的结果,是动态更新的 ,转换有两种转换模式:追加(Append)模式和撤回(Retract)模式:

    • 追加模式(Append Mode)

      用于表只会被插入(Insert)操作更改的场景

      DataStream<Row> resultStream = tableEnv.toAppendStream(resultTable, Row.class);
      
    • 撤回模式(Retract Mode)

      • 用于任何场景。有些类似于更新模式中 Retract 模式,它只有 Insert 和 Delete 两类操作
      • 得到的数据会增加一个 Boolean 类型的标识位(返回的第一个字段),用它来表示到底是新增的数据(Insert),还是被删除的数据(Delete)
DataStream<Tuple2<Boolean, Row>> aggResultStream = tableEnv
.toRetractStream(aggResultTable , Row.class);
  • 将 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");
    
  • 创建临时视图(Temporary View)

    • 基于 DataStream 创建临时视图

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

      tableEnv.createTemporaryView("sensorView", sensorTable);
      
  • 查看执行计划

    Table API 提供了一种机制来解释计算表的逻辑和优化查询计划,查看执行计划,可以通过 TableEnvironment.explain(table) 方法或

    TableEnvironment.explain() 方法完成,返回一个字符串,描述三个计划:

    • 优化的逻辑查询计划

    • 优化后的逻辑查询计划

    • 实际执行计划。

      String explaination = tableEnv.explain(resultTable);
      System.out.println(explaination);
      
      == Abstract Syntax Tree ==
      LogicalFilter(condition=[=($0, _UTF-16LE'sensor_6')])
      +- LogicalProject(id=[$0], temperature=[$2])
         +- LogicalTableScan(table=[[default_catalog, default_database, inputTable, source: [CsvTableSource(read fields: id, timestamp, temperature)]]])
      
      == Optimized Logical Plan ==
      Calc(select=[CAST(_UTF-16LE'sensor_6':VARCHAR(2147483647) CHARACTER SET "UTF-16LE") AS id, temperature], where=[=(id, _UTF-16LE'sensor_6')])
      +- LegacyTableSourceScan(table=[[default_catalog, default_database, inputTable, source: [CsvTableSource(read fields: id, temperature)]]], fields=[id, temperature])
      
      == Physical Execution Plan ==
      Stage 1 : Data Source
      	content : Source: Custom File source
      
      	Stage 2 : Operator
      		content : CsvTableSource(read fields: id, temperature)
      		ship_strategy : FORWARD
      
      		Stage 3 : Operator
      			content : SourceConversion(table=[default_catalog.default_database.inputTable, source: [CsvTableSource(read fields: id, temperature)]], fields=[id, temperature])
      			ship_strategy : FORWARD
      
      			Stage 4 : Operator
      				content : Calc(select=[CAST(_UTF-16LE'sensor_6':VARCHAR(2147483647) CHARACTER SET "UTF-16LE") AS id, temperature], where=[(id = _UTF-16LE'sensor_6')])
      				ship_strategy : FORWARD
      
  • 流处理和关系代数的区别
    在这里插入图片描述

  • 动态表(Dynamic Tables)

    动态表是 Flink 对流数据的 Table API 和 SQL 支持的核心概念

    • 与表示批处理数据的静态表不同,动态表是随时间变化的
    • 持续查询(Continuous Query)
      • 动态表可以像静态的批处理表一样进行查询,查询一个动态表会产生持续查询(Continuous Query)
      • 连续查询永远不会终止,并会生成另一个动态表
      • 查询会不断更新其动态结果表,以反映其动态输入表上的更改
        在这里插入图片描述
  • 动态表和持续查询

    流式表查询的处理过程:

    • 流被转换为动态表

      • 为了处理带有关系查询的流,必须先将其转换为表

      • 从概念上讲,流的每个数据记录,都被解释为对结果表的插入(Insert)修改操作
        在这里插入图片描述

    • 对动态表计算连续查询,生成新的动态表

      持续查询会在动态表上做计算处理,并作为结果生成新的动态表
      在这里插入图片描述

    • 生成的动态表被转换回流

      与常规的数据库表一样,动态表可以通过插入(Insert)、更新(Update)和删除(Delete)更改,进行持续的修改

      • 仅追加(Append-only)流

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

      • 撤回(Retract)流

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

      • Upsert(更新插入)流

        Upsert 流也包含两种类型的消息:Upsert 消息和删除(Delete)消息
        在这里插入图片描述

  • 时间特性(Time Attributes)

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

    • 处理时间

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

      • 由DataStream 转换成表时指定

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

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

        /**由datastream转换成表时指定*/
        Table sensorTable = tableEnv.fromDataStream(dataStream, 
        "id, temperature, timestamp, 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);
        
    • 事件时间

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

      • 由 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 中定义

在创建表的 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);
  • 窗口

    时间语义,要配合窗口操作才能发挥作用,在 Table API 和 SQL 中,主要有两种窗口:

    • Group Windows(分组窗口)

      根据时间或行计数间隔,将行聚合到有限的组(Group)中,并对每个组的数据执行一次聚合函数。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):滚动窗口要用 Tumble 类来定义

        // Tumbling Event-time Window
        .window(Tumble.over("10.minutes").on("rowtime").as("w"))
        // Tumbling Processing-time Window
        .window(Tumble.over("10.minutes").on("proctime").as("w"))
        // Tumbling Row-count Window
        .window(Tumble.over("10.rows").on("proctime").as("w"))
        
      • 滑动窗口(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"))
        
      • 会话窗口(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"))
        
    • SQL 中的 Group Windows:Group Windows 定义在 SQL 查询的 Group By 子句中

      • TUMBLE(time_attr, interval)

        定义一个滚动窗口,第一个参数是时间字段,第二个参数是窗口长度

      • HOP(time_attr, interval, interval)

        定义一个滑动窗口,第一个参数是时间字段,第二个参数是窗口滑动步长,第三个是窗口长度

      • SESSION(time_attr, interval)

        定义一个会话窗口,第一个参数是时间字段,第二个参数是窗口间隔

    • Over Windows

      针对每个输入行,计算相邻行范围内的聚合。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
        .window(Over.partitionBy("a").orderBy("rowtime").preceding(UNBOUNDED_RANGE).as("w"))
        //无界的处理时间 over window
        .window(Over.partitionBy("a").orderBy("proctime").preceding(UNBOUNDED_RANGE).as("w"))
        // 无界的事件时间 Row-count over window
        .window(Over.partitionBy("a").orderBy("rowtime").preceding(UNBOUNDED_ROW).as("w"))
        //无界的处理时间 Row-count over window
        .window(Over.partitionBy("a").orderBy("proctime").preceding(UNBOUNDED_ROW).as("w"))
        
      • 有界 Over Windows:有界的 over window 是用间隔的大小指定的

        // 有界的事件时间 over window
        .window(Over.partitionBy("a").orderBy("rowtime").preceding("1.minutes").as("w"))
        // 有界的处理时间 over window
        .window(Over.partitionBy("a").orderBy("proctime").preceding("1.minutes").as("w"))
        // 有界的事件时间 Row-count over window
        .window(Over.partitionBy("a").orderBy("rowtime").preceding("10.rows").as("w"))
        // 有界的处理时间 Row-count over window
        .window(Over.partitionBy("a").orderBy("procime").preceding("10.rows").as("w"))
        
    • 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
  • 函数(Functions)

    • 系统实现

      SQL 中支持的很多函数,Table API 和 SQL 都已经做了实现

    • 用户自定义

      用户定义函数(User-defined Functions,UDF)是一个重要的特性,它们显著地扩展了查询的表达能力。在大多数情况下,用户定义的函数必须先注册,然后才能在查询中使用。函数通过调用 registerFunction()方法在 TableEnvironment 中注册。当用户定义的函数被注册时,它被插入到 TableEnvironment 的函数目录中,这样Table API 或 SQL 解析器就可以识别并正确地解释它

      • 标量函数(Scalar Functions)

        用户定义的标量函数,可以将0、1或多个标量值,映射到新的标量值 ;为了定义标量函数,必须在 org.apache.flink.table.functions 中扩展基类

        Scalar Function,并实现(一个或多个)求值(eval)方法;标量函数的行为由求值方法决定,求值方法必须公开声明并命名为 eval

        public static class HashCode extends ScalarFunction {
        private int factor = 13;
        public HashCode(int factor) {
        this.factor = factor;
        }
        public int eval(String s) {
        return s.hashCode() * factor; } }
        
      • 表函数(Table Functions)

        用户定义的表函数,也可以将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<String, Integer>(s, s.length()));
        } } }
        
      • 聚合函数(Aggregate Functions)

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值