• 【翻译】Flink Table Api & SQL —— Table API


    本文翻译自官网:Table API  https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/table/tableApi.html

    Flink Table Api & SQL 翻译目录

    Table API是用于流和批处理的统一的关系API。 Table API查询可以在批处理或流输入上运行而无需修改。 Table API是SQL语言的超集,是专门为与Apache Flink配合使用而设计的。 Table API是用于Scala和Java的语言集成的API。 Table API查询不是将查询指定为SQL常见的String值,而是以Java或Scala中的语言嵌入样式定义,并具有IDE支持,例如自动完成和语法验证。

    Table API与Flink的SQL集成共享其API的许多概念和部分。 看一下Common Concepts&API,了解如何注册表或创建Table对象。 “流概念”页面讨论了流的特定概念,例如动态表和时间属性。

    下面的示例假定具有属性(a,b,c,rowtime)的已注册表 Orders。 rowtime字段是流中的逻辑时间属性,或者是批处理中的常规时间戳字段。

    概述与范例

     Table API可用于Scala和Java。 Scala Table API利用Scala表达式,Java Table API基于已解析并转换为等效表达式的字符串。

    以下示例显示了Scala和Java Table API之间的区别。 该表程序在批处理环境中执行。 它将扫描 Orders 表,按字段 a 进行分组,并计算每组的结果行。 该表程序的结果将转换为Row类型的数据集并进行打印。

    通过导入org.apache.flink.api.scala._和org.apache.flink.table.api.scala._来启用Scala Table API。

    以下示例显示了Scala Table API程序的构造方式。 表属性是使用Scala符号引用的,Scala符号以撇号(')开头。

    import org.apache.flink.api.scala._
    import org.apache.flink.table.api._
    import org.apache.flink.table.api.scala._
    
    // environment configuration
    val env = ExecutionEnvironment.getExecutionEnvironment
    val tEnv = BatchTableEnvironment.create(env)
    
    // register Orders table in table environment
    // ...
    
    // specify table program
    val orders = tEnv.scan("Orders") // schema (a, b, c, rowtime)
    
    val result = orders
                   .groupBy('a)
                   .select('a, 'b.count as 'cnt)
                   .toDataSet[Row] // conversion to DataSet
                   .print()

    下一个示例显示了一个更复杂的Table API程序。 程序再次扫描 Orders
    表。 它过滤空值,对String类型的字段 a 进行归一化,并针对每个小时计算并产生a平均帐单金额b。

    // environment configuration
    // ...
    
    // specify table program
    val orders: Table = tEnv.scan("Orders") // schema (a, b, c, rowtime)
    
    val result: Table = orders
            .filter('a.isNotNull && 'b.isNotNull && 'c.isNotNull)
            .select('a.lowerCase() as 'a, 'b, 'rowtime)
            .window(Tumble over 1.hour on 'rowtime as 'hourlyWindow)
            .groupBy('hourlyWindow, 'a)
            .select('a, 'hourlyWindow.end as 'hour, 'b.avg as 'avgBillingAmount)

    由于Table API是用于批处理和流数据的统一API,因此两个示例程序都可以在批处理和流输入上执行,而无需对表程序本身进行任何修改。 在这两种情况下,只要流记录不晚,程序都会产生相同的结果(有关详细信息,请参见流概念)。

    Operations

    Table API支持以下操作。 请注意,不是所有的操作都可以批量和流式传输。 它们被相应地标记。

    Scan, Projection, and Filter

    OperatorsDescription
    Scan
    Batch Streaming

    类似于SQL查询中的FROM子句。 扫描已注册的表。

    val orders: Table = tableEnv.scan("Orders")
    Select
    Batch Streaming

    类似于SQL SELECT语句。 执行选择操作。

    val orders: Table = tableEnv.scan("Orders")
    val result = orders.select('a, 'c as 'd)

    您可以使用星号(*)充当通配符,选择表中的所有列。

    val orders: Table = tableEnv.scan("Orders")
    val result = orders.select('*)
    As
    Batch Streaming

    Renames fields.

    val orders: Table = tableEnv.scan("Orders").as('x, 'y, 'z, 't)
    Where / Filter
    Batch Streaming

    类似于SQL WHERE子句。 过滤掉未通过过滤谓词的行。

    val orders: Table = tableEnv.scan("Orders")
    val result = orders.filter('a % 2 === 0)
    or
    val orders: Table = tableEnv.scan("Orders")
    val result = orders.where('b === "red")

    Column Operations

    OperatorsDescription
    AddColumns
    Batch Streaming

    执行字段添加操作。 如果添加的字段已经存在,它将引发异常。

    val orders = tableEnv.scan("Orders");
    val result = orders.addColumns(concat('c, "Sunny"))
    AddOrReplaceColumns
    Batch Streaming

    执行字段添加操作。 如果添加列名称与现有列名称相同,则现有字段将被替换。 此外,如果添加的字段具有重复的字段名称,则使用最后一个。

    val orders = tableEnv.scan("Orders");
    val result = orders.addOrReplaceColumns(concat('c, "Sunny") as 'desc)
    DropColumns
    Batch Streaming

    执行字段删除操作。 字段表达式应该是字段引用表达式,并且只能删除现有字段。

    val orders = tableEnv.scan("Orders");
    val result = orders.dropColumns('b, 'c)
    RenameColumns
    Batch Streaming

    执行字段重命名操作。 字段表达式应该是别名表达式,并且只能重命名现有字段。

    val orders = tableEnv.scan("Orders");
    val result = orders.renameColumns('b as 'b2, 'c as 'c2)

    Aggregations

    OperatorsDescription
    GroupBy Aggregation
    Batch Streaming
    Result Updating

    类似于SQL GROUP BY子句。 使用以下正在运行的聚合运算符将分组键上的行分组,以逐行聚合行。

    val orders: Table = tableEnv.scan("Orders")
    val result = orders.groupBy('a).select('a, 'b.sum as 'd)

    注意:对于流式查询,根据聚合的类型和不同的分组键的数量,计算查询结果所需的状态可能会无限增长。 请提供具有有效保留间隔的查询配置,以防止出现过多的状态。 有关详细信息,请参见查询配置

    GroupBy Window Aggregation
    Batch Streaming

    在 group window 和可能的一个或多个分组键上对表进行分组和聚集。

    val orders: Table = tableEnv.scan("Orders")
    val result: Table = orders
        .window(Tumble over 5.minutes on 'rowtime as 'w) // define window
        .groupBy('a, 'w) // group by key and window
        .select('a, w.start, 'w.end, 'w.rowtime, 'b.sum as 'd) // access window properties and aggregate
    Over Window Aggregation
    Streaming

    类似于SQL OVER子句。 基于前一行和后一行的窗口(范围),为每一行计算窗口聚合。 有关更多详细信息,请参见Windows部分

    val orders: Table = tableEnv.scan("Orders")
    val result: Table = orders
        // define window
        .window(Over  
          partitionBy 'a
          orderBy 'rowtime
          preceding UNBOUNDED_RANGE
          following CURRENT_RANGE
          as 'w)
        .select('a, 'b.avg over 'w, 'b.max over 'w, 'b.min over 'w) // sliding aggregate

    注意:必须在同一窗口(即相同的分区,排序和范围)上定义所有聚合。 当前,仅支持PRECEDING(无边界和有界)到CURRENT ROW范围的窗口。 目前尚不支持带有FOLLOWING的范围。 必须在单个时间属性上指定ORDER BY。

    Distinct Aggregation
    Batch Streaming 
    Result Updating

    类似于SQL DISTINCT AGGREGATION子句,例如COUNT(DISTINCT a)。 不同的聚合声明聚合函数(内置或用户定义的)仅应用于不同的输入值。 可以将不同应用于GroupBy聚合,GroupBy窗口聚合和Over Window聚合。

    val orders: Table = tableEnv.scan("Orders");
    // Distinct aggregation on group by
    val groupByDistinctResult = orders
        .groupBy('a)
        .select('a, 'b.sum.distinct as 'd)
    // Distinct aggregation on time window group by
    val groupByWindowDistinctResult = orders
        .window(Tumble over 5.minutes on 'rowtime as 'w).groupBy('a, 'w)
        .select('a, 'b.sum.distinct as 'd)
    // Distinct aggregation on over window
    val result = orders
        .window(Over
            partitionBy 'a
            orderBy 'rowtime
            preceding UNBOUNDED_RANGE
            as 'w)
        .select('a, 'b.avg.distinct over 'w, 'b.max over 'w, 'b.min over 'w)

    用户定义的聚合函数也可以与DISTINCT修饰符一起使用。 要仅针对不同值计算聚合结果,只需向聚合函数添加distinct修饰符即可。

    val orders: Table = tEnv.scan("Orders");
    
    // Use distinct aggregation for user-defined aggregate functions
    val myUdagg = new MyUdagg();
    orders.groupBy('users).select('users, myUdagg.distinct('points) as 'myDistinctResult);

    注意:对于流式查询,计算查询结果所需的状态可能会无限增长,具体取决于不同字段的数量。 请提供具有有效保留间隔的查询配置,以防止出现过多的状态。 有关详细信息,请参见查询配置。.

    Distinct
    Batch Streaming 
    Result Updating
    类似于SQL DISTINCT子句。 返回具有不同值组合的记录。val orders: Table = tableEnv.scan("Orders")
    val result = orders.distinct()

    注意:对于流式查询,计算查询结果所需的状态可能会无限增长,具体取决于不同字段的数量。 请提供具有有效保留间隔的查询配置,以防止出现过多的状态。 如果启用了状态清除功能,那么distinct必须发出消息,

    以防止下游运算符过早地退出状态,这会导致distinct包含结果更新。 有关详细信息,请参见查询配置。

    Joins

    OperatorsDescription
    Inner Join
    Batch Streaming

    类似于SQL JOIN子句。 连接两个表。 两个表必须具有不同的字段名称,并且至少一个相等的联接谓词必须通过联接运算符或使用where或filter运算符进行定义。

    val left = ds1.toTable(tableEnv, 'a, 'b, 'c)
    val right = ds2.toTable(tableEnv, 'd, 'e, 'f)
    val result = left.join(right).where('a === 'd).select('a, 'b, 'e)

    注意:对于流式查询,根据不同输入行的数量,计算查询结果所需的状态可能会无限增长。 请提供具有有效保留间隔的查询配置,以防止出现过多的状态。 有关详细信息,请参见查询配置。

    Outer Join
    Batch StreamingResult Updating

    类似于SQL LEFT / RIGHT / FULL OUTER JOIN子句。 连接两个表。 两个表必须具有不同的字段名称,并且必须至少定义一个相等联接谓词。

    val left = tableEnv.fromDataSet(ds1, 'a, 'b, 'c)
    val right = tableEnv.fromDataSet(ds2, 'd, 'e, 'f)
    
    val leftOuterResult = left.leftOuterJoin(right, 'a === 'd).select('a, 'b, 'e)
    val rightOuterResult = left.rightOuterJoin(right, 'a === 'd).select('a, 'b, 'e)
    val fullOuterResult = left.fullOuterJoin(right, 'a === 'd).select('a, 'b, 'e)

    注意:对于流式查询,根据不同输入行的数量,计算查询结果所需的状态可能会无限增长。 请提供具有有效保留间隔的查询配置,以防止出现过多的状态。 有关详细信息,请参见查询配置。

    Time-windowed Join
    Batch Streaming

    注意:时间窗口联接是可以以流方式处理的常规联接的子集。

    时间窗联接需要至少一个等联接谓词和在两侧限制时间的联接条件。 可以通过两个适当的范围谓词(<,<=,> =,>)或比较两个输入表的相同类型的时间属性(即处理时间或事件时间)的单个相等谓词来定义这种条件.

    例如,以下谓词是有效的窗口连接条件:

    • 'ltime === 'rtime
    • 'ltime >= 'rtime && 'ltime < 'rtime + 10.minutes
    val left = ds1.toTable(tableEnv, 'a, 'b, 'c, 'ltime.rowtime)
    val right = ds2.toTable(tableEnv, 'd, 'e, 'f, 'rtime.rowtime)
    
    val result = left.join(right)
      .where('a === 'd && 'ltime >= 'rtime - 5.minutes && 'ltime < 'rtime + 10.minutes)
      .select('a, 'b, 'e, 'ltime)
    Inner Join with Table Function (UDTF)
    Batch Streaming

    用表函数的结果联接表。 左(外)表的每一行都与表函数的相应调用产生的所有行连接在一起。 如果左表(外部)的表函数调用返回空结果,则该行将被删除。

    // instantiate User-Defined Table Function
    val split: TableFunction[_] = new MySplitUDTF()
    
    // join
    val result: Table = table
        .joinLateral(split('c) as ('s, 't, 'v))
        .select('a, 'b, 's, 't, 'v)
    Left Outer Join with Table Function (UDTF)
    Batch Streaming

    用表函数的结果联接表。 左(外)表的每一行都与表函数的相应调用产生的所有行连接在一起。 如果表函数调用返回空结果,则将保留对应的外部行,并用空值填充结果。

    注意:当前,左外部联接的表函数的谓词只能为空或字面值true。

    // instantiate User-Defined Table Function
    val split: TableFunction[_] = new MySplitUDTF()
    
    // join
    val result: Table = table
        .leftOuterJoinLateral(split('c) as ('s, 't, 'v))
        .select('a, 'b, 's, 't, 'v)
    Join with Temporal Table
    Streaming

    时态表是跟踪其随时间变化的表。

    时态表功能提供对特定时间点时态表状态的访问。 使用临时表函数联接表的语法与使用表函数进行内部联接的语法相同。

    当前仅支持使用临时表的内部联接。

    val ratesHistory = tableEnv.scan("RatesHistory")
    
    // register temporal table function with a time attribute and primary key
    val rates = ratesHistory.createTemporalTableFunction('r_proctime, 'r_currency)
    
    // join with "Orders" based on the time attribute and key
    val orders = tableEnv.scan("Orders")
    val result = orders
        .joinLateral(rates('o_rowtime), 'r_currency === 'o_currency)

    有关更多信息,请检查更详细的时态表概念描述。

    Set Operations

    OperatorsDescription
    Union
    Batch

    类似于SQL UNION子句。 合并两个已删除重复记录的表,两个表必须具有相同的字段类型。

    val left = ds1.toTable(tableEnv, 'a, 'b, 'c)
    val right = ds2.toTable(tableEnv, 'a, 'b, 'c)
    val result = left.union(right)
    UnionAll
    Batch Streaming

    类似于SQL UNION ALL子句。 合并两个表,两个表必须具有相同的字段类型。

    val left = ds1.toTable(tableEnv, 'a, 'b, 'c)
    val right = ds2.toTable(tableEnv, 'a, 'b, 'c)
    val result = left.unionAll(right)
    Intersect
    Batch

    类似于SQL INTERSECT子句。 相交返回两个表中都存在的记录。 如果一个记录在一个或两个表中多次出现,则仅返回一次,即结果表中没有重复的记录。 两个表必须具有相同的字段类型。

    val left = ds1.toTable(tableEnv, 'a, 'b, 'c)
    val right = ds2.toTable(tableEnv, 'e, 'f, 'g)
    val result = left.intersect(right)
    IntersectAll
    Batch

    类似于SQL INTERSECT ALL子句。 IntersectAll返回两个表中都存在的记录。 如果一个记录在两个表中都存在一次以上,则返回的次数与两个表中存在的次数相同,即,结果表可能具有重复的记录。 两个表必须具有相同的字段类型。

    val left = ds1.toTable(tableEnv, 'a, 'b, 'c)
    val right = ds2.toTable(tableEnv, 'e, 'f, 'g)
    val result = left.intersectAll(right)
    Minus
    Batch

    类似于SQL EXCEPT子句。 减号从左表返回不存在于右表中的记录。 左表中的重复记录仅返回一次,即删除了重复记录。 两个表必须具有相同的字段类型。

    val left = ds1.toTable(tableEnv, 'a, 'b, 'c)
    val right = ds2.toTable(tableEnv, 'a, 'b, 'c)
    val result = left.minus(right)
    MinusAll
    Batch

    类似于SQL EXCEPT ALL子句。 MinusAll返回右表中不存在的记录。 将返回(n-m)次在左侧表中出现n次,在右侧表中出现m次的记录,即删除与右侧表中存在的重复项一样多的记录。 两个表必须具有相同的字段类型。

    val left = ds1.toTable(tableEnv, 'a, 'b, 'c)
    val right = ds2.toTable(tableEnv, 'a, 'b, 'c)
    val result = left.minusAll(right)
    In
    Batch Streaming

    类似于SQL IN子句。 如果给定的表子查询中存在表达式,则In返回true。 子查询表必须由一列组成。 此列必须与表达式具有相同的数据类型。

    val left = ds1.toTable(tableEnv, 'a, 'b, 'c)
    val right = ds2.toTable(tableEnv, 'a)
    val result = left.select('a, 'b, 'c).where('a.in(right))

    注意:对于流查询,该操作将在联接和组操作中重写。 根据不同输入行的数量,计算查询结果所需的状态可能会无限增长。 请提供具有有效保留间隔的查询配置,以防止出现过多的状态。 有关详细信息,请参见查询配置。

    OrderBy, Offset & Fetch

    OperatorsDescription
    Order By
    Batch
    类似于SQL ORDER BY子句。 返回在所有并行分区上全局排序的记录。val in = ds.toTable(tableEnv, 'a, 'b, 'c)
    val result = in.orderBy('a.asc)
    Offset & Fetch
    Batch
    与SQL OFFSET和FETCH子句类似。 偏移量和提取限制从排序结果返回的记录数。 偏移和提取在技术上是Order By运算符的一部分,因此必须在其之前。

    val in = ds.toTable(tableEnv, 'a, 'b, 'c) // returns the first 5 records from the sorted result val result1: Table = in.orderBy('a.asc).fetch(5) // skips the first 3 records and returns all following records from the sorted result val result2: Table = in.orderBy('a.asc).offset(3) // skips the first 10 records and returns the next 5 records from the sorted result val result3: Table = in.orderBy('a.asc).offset(10).fetch(5)

    Insert

    OperatorsDescription
    Insert Into
    Batch Streaming

    与SQL查询中的INSERT INTO子句相似。 在已插入的输出表中执行插入。

    输出表必须在TableEnvironment中注册(请参阅注册TableSink)。 此外,已注册表的架构必须与查询的架构匹配。

    val orders: Table = tableEnv.scan("Orders") orders.insertInto("OutOrders")

    Group Windows

     Group 窗口根据时间或行计数间隔将组行聚合为有限的组,并每组评估一次聚合函数。 对于批处理表,窗口是按时间间隔对记录进行分组的便捷 shortcut 。

    Windows是使用window(w:GroupWindow)子句定义的,并且需要使用as子句指定的别名。 为了按窗口对表进行分组,必须像常规分组属性一样在groupBy(...)子句中引用窗口别名。 以下示例显示如何在表上定义窗口聚合。

    val table = input
      .window([w: GroupWindow] as 'w)  // define window with alias w
      .groupBy('w)   // group the table by window w
      .select('b.sum)  // aggregate

    在流式传输环境中,如果窗口聚合除对窗口进行分组以外,还对一个或多个属性进行分组,则它们只能并行计算,即groupBy(...)子句引用窗口别名和至少一个其他属性。 仅引用窗口别名的groupBy(...)子句(例如上例中的子句)只能由单个非并行任务求值。 以下示例显示如何使用其他分组属性定义窗口聚合。

    val table = input
      .window([w: GroupWindow] as 'w) // define window with alias w
      .groupBy('w, 'a)  // group the table by attribute a and window w 
      .select('a, 'b.sum)  // aggregate

    可以在select语句中将窗口属性(例如时间窗口的开始,结束或行时间时间戳)添加为窗口别名的属性,分别为w.start,w.end和w.rowtime。 窗口开始和行时间时间戳是包含窗口的上下边界。 相反,窗口结束时间戳是唯一的窗口上边界。 例如,从下午2点开始的30分钟滚动窗口将以14:00:00.000作为开始时间戳,以14:29:59.999作为行时间时间戳,以14:30:00.000作为结束时间戳。

    val table = input
      .window([w: GroupWindow] as 'w)  // define window with alias w
      .groupBy('w, 'a)  // group the table by attribute a and window w 
      .select('a, 'w.start, 'w.end, 'w.rowtime, 'b.count) // aggregate and add window start, end, and rowtime timestamps

    Window参数定义行如何映射到窗口。 窗口不是用户可以实现的接口。 相反,Table API提供了一组具有特定语义的预定义Window类,这些类被转换为基础的DataStream或DataSet操作。 支持的窗口定义在下面列出。

    Tumble (Tumbling Windows)

     滚动窗口将行分配给固定长度的非重叠连续窗口。 例如,5分钟的滚动窗口以5分钟为间隔对行进行分组。 可以在事件时间,处理时间或行数上定义滚动窗口。

    滚动窗口是使用Tumble类定义的,如下所示:

    MethodDescription
    over 将窗口的长度定义为时间或行计数间隔。
    on 用于分组(时间间隔)或排序(行计数)的时间属性。 对于批查询,它可以是任何Long或Timestamp属性。 对于流查询,它必须是声明的事件时间或处理时间时间属性。
    as 为窗口分配别名。 别名用于引用以下groupBy()子句中的窗口,并可以选择在select()子句中选择窗口属性,例如窗口开始,结束或行时间时间戳。
    // Tumbling Event-time Window
    .window(Tumble over 10.minutes on 'rowtime as 'w)
    
    // Tumbling Processing-time Window (assuming a processing-time attribute "proctime")
    .window(Tumble over 10.minutes on 'proctime as 'w)
    
    // Tumbling Row-count Window (assuming a processing-time attribute "proctime")
    .window(Tumble over 10.rows on 'proctime as 'w)

    Slide (Sliding Windows)

     滑动窗口的大小固定,并以指定的滑动间隔滑动。 如果滑动间隔小于窗口大小,则滑动窗口重叠。 因此,可以将行分配给多个窗口。 例如,一个15分钟大小的滑动窗口和5分钟的滑动间隔将每行分配给3个15分钟大小的不同窗口,它们以5分钟的间隔进行评估。 可以在事件时间,处理时间或行数上定义滑动窗口。

    滑动窗口是通过使用Slide类定义的,如下所示:

    MethodDescription
    over 将窗口的长度定义为时间或行计数间隔。
    every 将幻灯片间隔定义为时间间隔或行计数间隔。 滑动间隔必须与尺寸间隔具有相同的类型。
    on 用于分组(时间间隔)或排序(行计数)的时间属性。 对于批查询,它可以是任何Long或Timestamp属性。 对于流查询,它必须是声明的事件时间或处理时间时间属性。
    as 为窗口分配别名。 别名用于引用以下groupBy()子句中的窗口,并可以选择在select()子句中选择窗口属性,例如窗口开始,结束或行时间时间戳。
    // Sliding Event-time Window
    .window(Slide over 10.minutes every 5.minutes on 'rowtime as 'w)
    
    // Sliding Processing-time window (assuming a processing-time attribute "proctime")
    .window(Slide over 10.minutes every 5.minutes on 'proctime as 'w)
    
    // Sliding Row-count window (assuming a processing-time attribute "proctime")
    .window(Slide over 10.rows every 5.rows on 'proctime as 'w)

    Session (Session Windows)

     会话窗口没有固定的大小,但其边界由不活动的时间间隔定义,即,如果在定义的间隔时间段内未出现任何事件,则会话窗口关闭。 例如,间隔30分钟的会话窗口在30分钟不活动后观察到一行时开始(否则该行将被添加到现有窗口),如果在30分钟内未添加任何行,则关闭该窗口。 会话窗口可以在事件时间或处理时间工作。

    通过使用Session类定义会话窗口,如下所示:

    MethodDescription
    withGap 将两个窗口之间的间隔定义为时间间隔。
    on 用于分组(时间间隔)或排序(行计数)的时间属性。 对于批查询,它可以是任何Long或Timestamp属性。 对于流查询,它必须是声明的事件时间或处理时间时间属性。
    as 为窗口分配别名。 别名用于引用以下groupBy()子句中的窗口,并可以选择在select()子句中选择窗口属性,例如窗口开始,结束或行时间时间戳。
    // Session Event-time Window
    .window(Session withGap 10.minutes on 'rowtime as 'w)
    
    // Session Processing-time Window (assuming a processing-time attribute "proctime")
    .window(Session withGap 10.minutes on 'proctime as 'w)

    Over Windows

     窗口聚合是标准SQL(OVER子句)已知的,并在查询的SELECT子句中定义。 与在GROUP BY子句中指定的组窗口不同,在窗口上方不会折叠行。 取而代之的是,在窗口聚合中,为每个输入行在其相邻行的范围内计算一个聚合。

    使用window(w:OverWindow *)子句(在Python API中使用over_window(* OverWindow))定义窗口,并在select()方法中通过别名引用。 以下示例显示了如何在表上定义窗口聚合。

    val table = input
      .window([w: OverWindow] as 'w)              // define over window with alias w
      .select('a, 'b.sum over 'w, 'c.min over 'w) // aggregate over the over window w

    OverWindow定义了计算聚合的行范围。 OverWindow不是用户可以实现的接口。 相反,Table API提供了Over类来配置over窗口的属性。 可以在事件时间或处理时间以及指定为时间间隔或行计数的范围上定义窗口上方。 受支持的over窗口定义作为Over(和其他类)上的方法公开,并在下面列出:

    MethodRequiredDescription
    partitionBy Optional

    在一个或多个属性上定义输入的分区。 每个分区都经过单独排序,并且聚合函数分别应用于每个分区。

    注意:在流环境中,仅当窗口包含partition by子句时,才可以并行计算整个窗口聚合。 没有partitionBy(...),流将由单个非并行任务处理。

    orderBy Required

    定义每个分区内的行顺序,从而定义将聚合函数应用于行的顺序。

    注意:对于流查询,它必须是声明的事件时间或处理时间时间属性。 当前,仅支持单个sort属性。

    preceding Optional

    定义窗口中包含的并在当前行之前的行的间隔。 该间隔可以指定为时间间隔或行计数间隔。

    用时间间隔的大小指定窗口上的边界,例如,时间间隔为10分钟,行计数间隔为10行。

    使用常数来指定窗口上的无边界,即对于时间间隔为UNBOUNDED_RANGE或对于行计数间隔为UNBOUNDED_ROW。 Windows上的无边界从分区的第一行开始。

    如果省略了前面的子句,则将UNBOUNDED_RANGE和CURRENT_RANGE用作窗口的默认前后

    following Optional

    定义窗口中包含并紧随当前行的行的窗口间隔。 该间隔必须与前面的间隔(时间或行计数)以相同的单位指定。

    目前,不支持具有当前行之后的行的窗口。 相反,您可以指定两个常量之一:

    • CURRENT_ROW 将窗口的上限设置为当前行.
    • CURRENT_RANGE 将窗口的上限设置为当前行的排序键,即,与当前行具有相同排序键的所有行都包含在窗口中.

    如果省略以下子句,则将时间间隔窗口的上限定义为CURRENT_RANGE,将行计数间隔窗口的上限定义为CURRENT_ROW。

    as Required

    为上方窗口分配别名。 别名用于引用以下select()子句中的over窗口。

    注意:当前,同一select()调用中的所有聚合函数必须在相同的窗口范围内计算。

    Unbounded Over Windows

    // Unbounded Event-time over window (assuming an event-time attribute "rowtime")
    .window(Over partitionBy 'a orderBy 'rowtime preceding UNBOUNDED_RANGE as 'w)
    
    // Unbounded Processing-time over window (assuming a processing-time attribute "proctime")
    .window(Over partitionBy 'a orderBy 'proctime preceding UNBOUNDED_RANGE as 'w)
    
    // Unbounded Event-time Row-count over window (assuming an event-time attribute "rowtime")
    .window(Over partitionBy 'a orderBy 'rowtime preceding UNBOUNDED_ROW as 'w)
     
    // Unbounded Processing-time Row-count over window (assuming a processing-time attribute "proctime")
    .window(Over partitionBy 'a orderBy 'proctime preceding UNBOUNDED_ROW as 'w)

    Bounded Over Windows

    // Bounded Event-time over window (assuming an event-time attribute "rowtime")
    .window(Over partitionBy 'a orderBy 'rowtime preceding 1.minutes as 'w)
    
    // Bounded Processing-time over window (assuming a processing-time attribute "proctime")
    .window(Over partitionBy 'a orderBy 'proctime preceding 1.minutes as 'w)
    
    // Bounded Event-time Row-count over window (assuming an event-time attribute "rowtime")
    .window(Over partitionBy 'a orderBy 'rowtime preceding 10.rows as 'w)
      
    // Bounded Processing-time Row-count over window (assuming a processing-time attribute "proctime")
    .window(Over partitionBy 'a orderBy 'proctime preceding 10.rows as 'w)

    Row-based Operations

    基于行的操作生成具有多列的输出。

    OperatorsDescription
    Map
    Batch Streaming

    使用用户定义的标量函数或内置标量函数执行映射操作。 如果输出类型是复合类型,则输出将被展平.

    class MyMapFunction extends ScalarFunction {
      def eval(a: String): Row = {
        Row.of(a, "pre-" + a)
      }
    
      override def getResultType(signature: Array[Class[_]]): TypeInformation[_] =
        Types.ROW(Types.STRING, Types.STRING)
    }
    
    val func = new MyMapFunction()
    val table = input
      .map(func('c)).as('a, 'b)
    FlatMap
    Batch Streaming

    使用表格功能执行flatMap操作.

    class MyFlatMapFunction extends TableFunction[Row] {
      def eval(str: String): Unit = {
        if (str.contains("#")) {
          str.split("#").foreach({ s =>
            val row = new Row(2)
            row.setField(0, s)
            row.setField(1, s.length)
            collect(row)
          })
        }
      }
    
      override def getResultType: TypeInformation[Row] = {
        Types.ROW(Types.STRING, Types.INT)
      }
    }
    
    val func = new MyFlatMapFunction
    val table = input
      .flatMap(func('c)).as('a, 'b)
    Aggregate
    Batch StreamingResult Updating

    使用聚合函数执行聚合操作。 您必须使用select语句关闭“聚合”,并且select语句不支持聚合功能。 如果输出类型是复合类型,则聚合的输出将被展平.

    case class MyMinMaxAcc(var min: Int, var max: Int)
    
    class MyMinMax extends AggregateFunction[Row, MyMinMaxAcc] {
    
      def accumulate(acc: MyMinMaxAcc, value: Int): Unit = {
        if (value < acc.min) {
          acc.min = value
        }
        if (value > acc.max) {
          acc.max = value
        }
      }
    
      override def createAccumulator(): MyMinMaxAcc = MyMinMaxAcc(0, 0)
      
      def resetAccumulator(acc: MyMinMaxAcc): Unit = {
        acc.min = 0
        acc.max = 0
      }
    
      override def getValue(acc: MyMinMaxAcc): Row = {
        Row.of(Integer.valueOf(acc.min), Integer.valueOf(acc.max))
      }
    
      override def getResultType: TypeInformation[Row] = {
        new RowTypeInfo(Types.INT, Types.INT)
      }
    }
    
    val myAggFunc = new MyMinMax
    val table = input
      .groupBy('key)
      .aggregate(myAggFunc('a) as ('x, 'y))
      .select('key, 'x, 'y)
    Group Window Aggregate
    Batch Streaming

    在组窗口和可能的一个或多个分组键上对表进行分组和聚集。 您必须使用select语句关闭“聚合”。 并且select语句不支持“ *”或聚合函数.

    val myAggFunc = new MyMinMax
    val table = input
        .window(Tumble over 5.minutes on 'rowtime as 'w) // define window
        .groupBy('key, 'w) // group by key and window
        .aggregate(myAggFunc('a) as ('x, 'y))
        .select('key, 'x, 'y, 'w.start, 'w.end) // access window properties and aggregate results
    FlatAggregate
    Streaming
    Result Updating

    类似于GroupBy聚合。 使用以下运行表聚合运算符将分组键上的行分组,以逐行聚合行。 与AggregateFunction的区别在于TableAggregateFunction可以为一个组返回0个或更多记录。 您必须使用select语句关闭“ flatAggregate”。 并且select语句不支持聚合函数.

    除了使用emitValue输出结果外,还可以使用emitUpdateWithRetract方法。 与emittValue不同,emitUpdateWithRetract用于发出已更新的值。 此方法在撤消模式下增量输出数据,即,一旦有更新,我们就必须在发送新的更新记录之前撤回旧记录。 如果在表聚合函数中定义了这两种方法,则将优先使用emitUpdateWithRetract方法,因为这两种方法比emitValue更有效,因为它可以增量输出值。 有关详细信息,请参见表聚合函数

    import java.lang.{Integer => JInteger}
    import org.apache.flink.table.api.Types
    import org.apache.flink.table.functions.TableAggregateFunction
    
    /**
     * Accumulator for top2.
     */
    class Top2Accum {
      var first: JInteger = _
      var second: JInteger = _
    }
    
    /**
     * The top2 user-defined table aggregate function.
     */
    class Top2 extends TableAggregateFunction[JTuple2[JInteger, JInteger], Top2Accum] {
    
      override def createAccumulator(): Top2Accum = {
        val acc = new Top2Accum
        acc.first = Int.MinValue
        acc.second = Int.MinValue
        acc
      }
    
      def accumulate(acc: Top2Accum, v: Int) {
        if (v > acc.first) {
          acc.second = acc.first
          acc.first = v
        } else if (v > acc.second) {
          acc.second = v
        }
      }
    
      def merge(acc: Top2Accum, its: JIterable[Top2Accum]): Unit = {
        val iter = its.iterator()
        while (iter.hasNext) {
          val top2 = iter.next()
          accumulate(acc, top2.first)
          accumulate(acc, top2.second)
        }
      }
    
      def emitValue(acc: Top2Accum, out: Collector[JTuple2[JInteger, JInteger]]): Unit = {
        // emit the value and rank
        if (acc.first != Int.MinValue) {
          out.collect(JTuple2.of(acc.first, 1))
        }
        if (acc.second != Int.MinValue) {
          out.collect(JTuple2.of(acc.second, 2))
        }
      }
    }
    
    val top2 = new Top2
    val orders: Table = tableEnv.scan("Orders")
    val result = orders
        .groupBy('key)
        .flatAggregate(top2('a) as ('v, 'rank))
        .select('key, 'v, 'rank)

    Note: 对于流查询,根据聚合类型和不同的分组键的数量,计算查询结果所需的状态可能会无限增长。 请提供具有有效保留间隔的查询配置,以防止出现过多的状态。 有关详细信息,请参见查询配置。

    Group Window FlatAggregate
    Streaming

    在组窗口和可能的一个或多个分组键上对表进行分组和聚集。 您必须使用select语句关闭“ flatAggregate”。 并且select语句不支持聚合函数.

    val top2 = new Top2
    val orders: Table = tableEnv.scan("Orders")
    val result = orders
        .window(Tumble over 5.minutes on 'rowtime as 'w) // define window
        .groupBy('a, 'w) // group by key and window
        .flatAggregate(top2('b) as ('v, 'rank))
        .select('a, w.start, 'w.end, 'w.rowtime, 'v, 'rank) // access window properties and aggregate results

    Data Types

    请参阅有关数据类型的专用页面。

    通用类型和(嵌套的)复合类型(例如POJO,元组,行,Scala case class)也可以是一行的字段。

    可以使用值访问功能访问具有任意嵌套的复合类型的字段。

    泛型类型被视为黑盒,可以通过用户定义的函数传递或处理。

    Expression Syntax

    上一节中的某些运算符期望一个或多个表达式。 可以使用嵌入式Scala DSL或字符串指定表达式。 请参考上面的示例以了解如何指定表达式。

    这是用于表达式的EBNF语法:

    expressionList = expression , { "," , expression } ;
    
    expression = overConstant | alias ;
    
    alias = logic | ( logic , "as" , fieldReference ) | ( logic , "as" , "(" , fieldReference , { "," , fieldReference } , ")" ) ;
    
    logic = comparison , [ ( "&&" | "||" ) , comparison ] ;
    
    comparison = term , [ ( "=" | "==" | "===" | "!=" | "!==" | ">" | ">=" | "<" | "<=" ) , term ] ;
    
    term = product , [ ( "+" | "-" ) , product ] ;
    
    product = unary , [ ( "*" | "/" | "%") , unary ] ;
    
    unary = [ "!" | "-" | "+" ] , composite ;
    
    composite = over | suffixed | nullLiteral | prefixed | atom ;
    
    suffixed = interval | suffixAs | suffixCast | suffixIf | suffixDistinct | suffixFunctionCall | timeIndicator ;
    
    prefixed = prefixAs | prefixCast | prefixIf | prefixDistinct | prefixFunctionCall ;
    
    interval = timeInterval | rowInterval ;
    
    timeInterval = composite , "." , ("year" | "years" | "quarter" | "quarters" | "month" | "months" | "week" | "weeks" | "day" | "days" | "hour" | "hours" | "minute" | "minutes" | "second" | "seconds" | "milli" | "millis") ;
    
    rowInterval = composite , "." , "rows" ;
    
    suffixCast = composite , ".cast(" , dataType , ")" ;
    
    prefixCast = "cast(" , expression , dataType , ")" ;
    
    dataType = "BYTE" | "SHORT" | "INT" | "LONG" | "FLOAT" | "DOUBLE" | "BOOLEAN" | "STRING" | "DECIMAL" | "SQL_DATE" | "SQL_TIME" | "SQL_TIMESTAMP" | "INTERVAL_MONTHS" | "INTERVAL_MILLIS" | ( "MAP" , "(" , dataType , "," , dataType , ")" ) | ( "PRIMITIVE_ARRAY" , "(" , dataType , ")" ) | ( "OBJECT_ARRAY" , "(" , dataType , ")" ) ;
    
    suffixAs = composite , ".as(" , fieldReference , ")" ;
    
    prefixAs = "as(" , expression, fieldReference , ")" ;
    
    suffixIf = composite , ".?(" , expression , "," , expression , ")" ;
    
    prefixIf = "?(" , expression , "," , expression , "," , expression , ")" ;
    
    suffixDistinct = composite , "distinct.()" ;
    
    prefixDistinct = functionIdentifier , ".distinct" , [ "(" , [ expression , { "," , expression } ] , ")" ] ;
    
    suffixFunctionCall = composite , "." , functionIdentifier , [ "(" , [ expression , { "," , expression } ] , ")" ] ;
    
    prefixFunctionCall = functionIdentifier , [ "(" , [ expression , { "," , expression } ] , ")" ] ;
    
    atom = ( "(" , expression , ")" ) | literal | fieldReference ;
    
    fieldReference = "*" | identifier ;
    
    nullLiteral = "nullOf(" , dataType , ")" ;
    
    timeIntervalUnit = "YEAR" | "YEAR_TO_MONTH" | "MONTH" | "QUARTER" | "WEEK" | "DAY" | "DAY_TO_HOUR" | "DAY_TO_MINUTE" | "DAY_TO_SECOND" | "HOUR" | "HOUR_TO_MINUTE" | "HOUR_TO_SECOND" | "MINUTE" | "MINUTE_TO_SECOND" | "SECOND" ;
    
    timePointUnit = "YEAR" | "MONTH" | "DAY" | "HOUR" | "MINUTE" | "SECOND" | "QUARTER" | "WEEK" | "MILLISECOND" | "MICROSECOND" ;
    
    over = composite , "over" , fieldReference ;
    
    overConstant = "current_row" | "current_range" | "unbounded_row" | "unbounded_row" ;
    
    timeIndicator = fieldReference , "." , ( "proctime" | "rowtime" ) ;

    Literals: 在这里,文字是有效的Java文字。 字符串文字可以使用单引号或双引号指定。 复制引号以进行转义(例如,'It''s me.' or "I ""like"" dogs.")。

    Null literals: 空文字必须附加一个类型。 使用nullOf(type)(例如nullOf(INT))创建空值。

    Field references: fieldReference指定数据中的一列(如果使用*,则指定所有列),而functionIdentifier指定受支持的标量函数。 列名和函数名遵循Java标识符语法。

    Function calls: 指定为字符串的表达式也可以使用前缀表示法而不是后缀表示法来调用运算符和函数。

    Decimals: 如果需要使用精确的数值或大的十进制数,则Table API还支持Java的BigDecimal类型。 在Scala Table API中,小数可以由BigDecimal(“ 123456”)定义,而在Java中,可以通过附加“ p”来精确定义例如 123456p

    Time representation: 为了使用时间值,Table API支持Java SQL的日期,时间和时间戳类型。 在Scala Table API中,可以使用java.sql.Date.valueOf(“ 2016-06-27”),java.sql.Time.valueOf(“ 10:10:42”)或java.sql定义文字。 Timestamp.valueOf(“ 2016-06-27 10:10:42.123”)。 Java和Scala表API还支持调用“ 2016-06-27” .toDate(),“ 10:10:42” .toTime()和“ 2016-06-27 10:10:42.123” .toTimestamp() 用于将字符串转换为时间类型。 注意:由于Java的时态SQL类型取决于时区,因此请确保Flink Client和所有TaskManager使用相同的时区。

    Temporal intervals:  时间间隔可以表示为月数(Types.INTERVAL_MONTHS)或毫秒数(Types.INTERVAL_MILLIS)。 可以添加或减去相同类型的间隔(例如1.小时+ 10分钟)。 可以将毫秒间隔添加到时间点(例如“ 2016-08-10” .toDate + 5.days)。

    Scala expressions:  Scala表达式使用隐式转换。 因此,请确保将通配符导入org.apache.flink.table.api.scala._添加到程序中。 如果文字不被视为表达式,请使用.toExpr(如3.toExpr)强制转换文字。

    欢迎关注Flink菜鸟公众号,会不定期更新Flink(开发技术)相关的推文

    注意:在流环境中,仅当窗口包含partition by子句时,才可以并行计算整个窗口聚合。 没有partitionBy(...),流将由单个非并行任务处理。

  • 相关阅读:
    第52周二Restful
    第52周一
    第51周日
    第51周六
    第51周五
    第51周四
    第51周三
    第51周二
    第51周一
    第50周日
  • 原文地址:https://www.cnblogs.com/Springmoon-venn/p/11884879.html
Copyright © 2020-2023  润新知