原创

Flink Table Api详解(算子)

该文章主要是对Flink官网相关内容进行翻译,原文地址:https://ci.apache.org/projects/flink/flink-docs-release-1.10/dev/table/tableApi.html#over-windows

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对象。“Streaming Concepts”页面讨论了流的特定概念,例如动态表和时间属性。下面的示例假定具有属性(a, b, c, rowtime)的称为Orders的已注册表。rowtime字段是流中的逻辑时间属性,或者是批处理中的常规时间戳记字段。

概述与示例

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

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

Java

通过导入org.apache.flink.table.api.java._启用Java Table API。以下示例显示了如何构造Java Table API程序以及如何将表达式指定为字符串。

import org.apache.flink.table.api._
import org.apache.flink.table.api.java._

// environment configuration
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
BatchTableEnvironment tEnv = BatchTableEnvironment.create(env);

// register Orders table in table environment
// ...

// specify table program
Table orders = tEnv.from("Orders"); // schema (a, b, c, rowtime)

Table counts = orders
        .groupBy("a")
        .select("a, b.count as cnt");

// conversion to DataSet
DataSet<Row> result = tEnv.toDataSet(counts, Row.class);
result.print();

Scala

通过导入org.apache.flink.api.scala._和org.apache.flink.table.api.scala._来启用Scala Table API。以下示例显示了Scala Table API程序的构造方式。使用Scala符号引用Table属性,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.from("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。

Java

// environment configuration
// ...

// specify table program
Table orders = tEnv.from("Orders"); // schema (a, b, c, rowtime)

Table result = 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");

Scala

// environment configuration
// ...

// specify table program
val orders: Table = tEnv.from("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,因此两个示例程序都可以在批处理和流输入上执行,而无需对Table程序本身进行任何修改。在这两种情况下,只要流记录不晚,程序都会产生相同的结果。

算子

Table API支持以下算子操作。 请注意,并非所有操作都可用于批处理和流式处理; 他们被相应地标记。

Scan, Projection, and Filter

Operators Description
From
Batch
Streaming
与SQL查询中的FROM子句类似。 执行已注册表的扫描。
val orders: Table = tableEnv.from("Orders")
Select
Batch
Streaming
与SQL SELECT语句类似。 执行选择操作。
val orders: Table = tableEnv.from("Orders")
val result = orders.select('a, 'c as 'd)
可以使用星号(*)作为通配符,选择表格中的所有列。
val orders: Table = tableEnv.from("Orders")
val result = orders.select('*)
As
Batch
Streaming
重命名字段。
val orders: Table = tableEnv.from("Orders").as('x, 'y, 'z, 't)
Where / Filter
Batch
Streaming
与SQL WHERE子句类似。 过滤掉未通过过滤谓词的行。
val orders: Table = tableEnv.from("Orders")
val result = orders.filter('a % 2 === 0)
或者val orders: Table = tableEnv.from("Orders")
val result = orders.where('b === "red")

Column Operations

Operators Description
AddColumns
Batch
Streaming
执行字段添加操作。如果添加的字段已经存在,它将引发异常。
val orders = tableEnv.from("Orders");
val result = orders.addColumns(concat('c, "Sunny"))
AddOrReplaceColumns
Batch
Streaming
执行字段添加操作。如果添加列名称与现有列名称相同,则现有字段将被替换。此外,如果添加的字段具有重复的字段名称,则使用最后一个。
val orders = tableEnv.from("Orders");
val result = orders.addOrReplaceColumns(concat('c, "Sunny") as 'desc)
DropColumns
Batch
Streaming
执行字段删除操作。字段表达式应该是字段引用表达式,并且只能删除现有字段。
val orders = tableEnv.from("Orders");
val result = orders.dropColumns('b, 'c)
RenameColumns
Batch
Streaming
执行字段重命名操作。字段表达式应该是别名表达式,并且只有现有字段可以重命名。
val orders = tableEnv.from("Orders");
val result = orders.renameColumns('b as 'b2, 'c as 'c2)

Aggregations

Operators Description
GroupBy聚合
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窗口聚合
Batch
Streaming
在组窗口可能的一个或多个分组key上对表进行分组和聚集。
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 窗口聚合
Batch
Streaming
类似于SQL OVER子句。基于前一行和后一行的窗口(范围),为每一行计算窗口聚合。
val orders: Table = tableEnv.from("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
**Note:**必须在同一窗口(即相同的分区,排序和范围)上定义所有聚合。当前,仅支持PRECEDING(无边界和有界)到CURRENT ROW范围的窗口。目前尚不支持带有FOLLOWING的范围。必须在单个时间属性上指定ORDER BY。
Distinct聚合
Batch
Streaming
Result Updating
类似于SQL DISTINCT AGGREGATION子句,例如COUNT(DISTINCT a)。不同的聚合声明聚合函数(内置或用户定义的)仅应用于不同的输入值。Distinct聚合可以用于GroupBy聚合,GroupBy窗口聚合和Over窗口聚合。
val orders: Table = tableEnv.from("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.from("Orders");

// Use distinct aggregation for user-defined aggregate functions
val myUdagg = new MyUdagg();
orders.groupBy('users).select('users, myUdagg.distinct('points) as 'myDistinctResult);
**Note:**对于流式查询,计算查询结果所需的状态可能会无限增长,具体取决于聚合类型和不同分组键的数量。 请提供具有有效保留间隔的查询配置,以防止状态过大。 请参阅查询配置
Distinct
Batch
Streaming
Result Updating
类似于SQL DISTINCT子句。返回具有不同值组合的记录。
val orders: Table = tableEnv.from("Orders")
val result = orders.distinct()
**Note:**对于流查询,根据查询字段的数量,计算查询结果所需的状态可能会无限增长。请提供具有有效保留间隔的查询配置,以防止出现过多的状态。如果启用了状态清除功能,那么distinct必须发出消息,以防止下游运算符过早地退出状态,这会导致distinct包含结果更新。有关详细信息,请参阅查询配置

Joins

Operators Description
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)
Note: 对于流查询,根据不同输入行的数量,计算查询结果所需的状态可能会无限增长。请提供具有有效保留间隔的查询配置,以防止出现过多的状态。有关详细信息,请参阅查询配置
Outer Join
Batch
Streaming
Result 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)
Note: 对于流查询,根据不同输入行的数量,计算查询结果所需的状态可能会无限增长。请提供具有有效保留间隔的查询配置,以防止出现过多的状态。有关详细信息,请参阅查询配置
Time-windowed Join
Batch
Streaming
Note: 时间窗口连接是可以以流方式处理的常规子集连接。
时间窗口连接需要至少一个等连接和一个限制双方时间的连接条件。可以通过两个适当的范围谓词(<,<=,> =,>)或比较两个输入表的相同类型的时间属性(即处理时间或事件时间)的单个相等谓词来定义这种条件。
例如,以下是有效的窗口连接条件:
'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
使用表函数的结果连接表。 左(外)表的每一行与表函数的相应调用产生的所有行连接。 如果表函数调用返回空结果,则保留相应的外部行,并使用空值填充结果。
**Note:**目前,表函数的左外连接只能为空或为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
时态表是跟踪其随时间变化的表。
时态表功能提供对特定时间点时态表状态的访问。使用时态表函数联接表的语法与使用表函数进行内部联接的语法相同。
**Note:**当前仅支持使用临时表的内部联接。
val ratesHistory = tableEnv.from("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.from("Orders")
val result = orders
.joinLateral(rates('o_rowtime), 'r_currency === 'o_currency)

集合算子

Operators Description
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子句。Minus返回左表中右表中不存在的记录。左表中的重复记录只返回一次,即删除重复项。 两个表必须具有相同的字段类型。
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子句类似。 如果表达式存在于给定的表子查询中,则返回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))
Note: 对于流查询,该操作将被重写为join and group操作。根据不同的输入行的数量,计算查询结果所需的状态可能会无限增长。请提供具有有效保留间隔的查询配置,以防止出现过多的状态。有关详细信息,请参阅查询配置

OrderBy, Offset & Fetch

Operators Description
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

Operators Description
Insert Into
Batch
Streaming
与SQL查询中的INSERT INTO子句相似。在已插入的输出表中执行插入。
输出表必须在TableEnvironment中注册。此外,已注册表的模式必须与查询的模式匹配。
val orders: Table = tableEnv.from("Orders")
orders.insertInto("OutOrders")

Group Windows

组窗口根据时间或行计数(row-count )间隔将行组聚合为有限组,并按组聚合函数。 对于批处理表,窗口是按时间间隔对记录进行分组的便捷快捷方式。

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 (滚动窗口)

滚动窗口将行分配给固定长度的不重叠的连续窗口。

例如,5分钟的翻滚窗口以5分钟为间隔对行进行分组。 可以在事件时间,处理时间或行数上定义翻滚窗口。使用Tumble类定义翻滚窗口如下:

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

Method Description
over 定义窗口的长度,可以是时间间隔也可以是行数间隔。
on 时间属性为组(时间间隔)或排序(行计数)。 对于批处理查询,这可能是任何Long或Timestamp属性。 对于流式查询,这必须是声明的事件时间或处理时间属性。
as 为窗口指定别名。 别名用于引用以下groupBy()子句中的窗口,并可选择在select()子句中选择窗口属性,如window start,end或rowtime timestamp。
/ 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 (滑动窗口)

滑动窗口的大小固定,并以指定的滑动间隔滑动。如果滑动间隔小于窗口大小,则滑动窗口重叠。因此,可以将行分配给多个窗口。

例如,15分钟大小和5分钟滑动间隔的滑动窗口将每行分配给3个不同的15分钟大小的窗口,这些窗口以5分钟的间隔进行调用。 可以在事件时间,处理时间或行数上定义滑动窗口。

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

Method Description
over 定义窗口的长度,可以是时间或行计数间隔。
every 定义滑动间隔,可以是时间间隔也可以是行数。 滑动间隔必须与大小间隔的类型相同。
on 时间属性为组(时间间隔)或排序(行计数)。 对于批处理查询,这可能是任何Long或Timestamp属性。 对于流式查询,这必须是声明的事件时间或处理时间属性
as 为窗口指定别名。 别名用于引用以下groupBy()子句中的窗口,并可选择在select()子句中选择窗口属性,如window start,end或rowtime timestamp。
// 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 (会话窗口)

会话窗口没有固定的大小,但是其边界由不活动的时间间隔定义,即如果在定义的间隔时间内没有事件出现,则会话窗口关闭。

例如,间隔30分钟的会话窗口会在30分钟不活动后观察到一行(否则该行将被添加到现有窗口)后开始,如果30分钟内未添加任何行,则关闭该窗口。会话窗口可以在事件时间或处理时间工作。

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

Method Description
withGap 将两个窗口之间的间隔定义为时间间隔。
on 时间属性为组(时间间隔)或排序(行计数)。 对于批处理查询,这可能是任何Long或Timestamp属性。 对于流式查询,这必须是声明的事件时间或处理时间属性。
as 为窗口指定别名。 别名用于引用以下groupBy()子句中的窗口,并可选择在select()子句中选择窗口属性,如window start,end或rowtime timestamp。
// 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(和其他类)上的方法公开,并在下面列出:

Method Required Description
partitionBy Optional 在一个或多个属性上定义输入的分区。每个分区都经过单独排序,并且聚合函数分别应用于每个分区。
Note: 在流环境中,如果窗口包含partition by子句,则只能并行计算整个窗口聚合。没有partitionBy(…),流将由单个非并行任务处理。
orderBy Required 定义每个分区内的行顺序,从而定义将聚合函数应用于行的顺序。
Note: 对于流查询,它必须是声明的事件时间或处理时间时间属性。当前,仅支持单个sort属性。
preceding Optional 定义窗口中包含的并在当前行之前的行的间隔。该间隔可以指定为时间间隔或行计数间隔。
用时间间隔的大小指定窗口上的边界,例如,时间间隔为10分钟,行计数间隔为10行。
使用常数指定窗口上的无边界,即对于时间间隔为UNBOUNDED_RANGE或对于行计数间隔为UNBOUNDED_ROW。Windows上的无边界从分区的第一行开始。
如果省略了前面的子句,则将UNBOUNDED_RANGE和CURRENT_RANGE用作窗口的默认前后。
following Optional 定义窗口中包含并紧随当前行的行的窗口间隔。该间隔必须与前面的间隔(时间或行计数)以相同的单位指定。目前,不支持具有当前行之后的行的窗口。而是可以指定两个常量之一:
1. CURRENT_ROW将窗口的上限设置为当前行。
2. 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)

基于行的操作

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

  • 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 Streaming Result 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 Batch Streaming

    类似于GroupBy聚合。使用以下运行表聚合运算符将分组键上的行分组,以逐行聚合行。与AggregateFunction的区别在于TableAggregateFunction可以为一个组返回0个或更多记录。您必须使用select语句关闭“ flatAggregate”。并且select语句不支持聚合函数。除了使用emitValue来输出结果之外,还可以使用emitUpdateWithRetract方法。与emitValue不同,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.from("Orders")
    val result = orders
        .groupBy('key)
        .flatAggregate(top2('a) as ('v, 'rank))
        .select('key, 'v, 'rank)
    

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

  • Group Window FlatAggregate Batch Streaming

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

    val top2 = new Top2
    val orders: Table = tableEnv.from("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案例类)也可以是一行的字段。可以使用值访问功能访问具有任意嵌套的复合类型的字段。泛型类型被视为黑盒,可以通过用户定义的函数传递或处理。

表达式语法

前面几节中的某些运算符期望一个或多个表达式。可以使用嵌入式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" ) ;

文字:这里的文字是有效的Java文字。字符串文字可以使用单引号或双引号指定。复制引号以进行转义(例如“是我。”或“我”喜欢”狗。”)。

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

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

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

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

时间表示:为了使用时间值,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使用相同的时区。

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

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

正文到此结束
本文目录