Table API是用于流和批处理的统一关系API。表API查询可以在批处理或流输入上运行,而无需修改。Table API是SQL语言的超集,专为与Apache Flink一起工作而设计。Table API是Scala、Java和Python的语言集成API。与将查询指定为SQL中常见的字符串值不同,表API查询在Java、Scala或Python中以语言嵌入的方式定义,并具有自动完成和语法验证等IDE支持。
Table API与Flink的SQL集成共享其API的许多概念和部分。看一下公共概念和API,了解如何注册表或创建Table
对象。流概念页面讨论了流特定的概念,如动态表和时间属性。
下面的示例假设一个名为Orders
的注册表,它具有属性(a
、b
、c
、rowtime
)。rowtime
字段可以是流中的逻辑时间属性,也可以是批处理中的常规时间戳字段。
1、概述和示例
Table API可用于Scala、Java和Python。Scala表API利用Scala表达式,Java表API支持表达式DSL和解析并转换为等效表达式的字符串,Python表API目前只支持解析并转换为等效表达式的字符串。
下面的例子展示了Scala、Java和Python表API之间的区别。表程序在批处理环境中执行。它扫描Orders
表,按字段a
分组,并对每个组的结果行进行计数。
Java Table API通过导入org.apache.flink.table.api.java.*
来启用。下面的示例展示了如何构造Java Table API程序以及如何将表达式指定为字符串。对于表达式DSL,还需要导入静态的org.apache.flink.table.api.Expressions.*
java
import org.apache.flink.table.api.*;
import static org.apache.flink.table.api.Expressions.*;
EnvironmentSettings settings = EnvironmentSettings
.newInstance()
.inStreamingMode()
.build();
TableEnvironment tEnv = TableEnvironment.create(settings);
// 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"));
// print
counts.execute().print();
下一个示例展示了一个更复杂的Table API程序。程序再次扫描Orders
表。它过滤null
值,规范化字符串类型的字段a
,并计算每个小时和产品a
的平均计费金额b
。
java
// environment configuration
// ...
// specify table program
Table orders = tEnv.from("Orders"); // schema (a, b, c, rowtime)
Table result = orders
.filter(
and(
$("a").isNotNull(),
$("b").isNotNull(),
$("c").isNotNull()
))
.select($("a").lowerCase().as("a"), $("b"), $("rowtime"))
.window(Tumble.over(lit(1).hours()).on($("rowtime")).as("hourlyWindow"))
.groupBy($("hourlyWindow"), $("a"))
.select($("a"), $("hourlyWindow").end().as("hour"), $("b").avg().as("avgBillingAmount"));
由于Table API是批处理和流数据的统一API,因此两个示例程序都可以在批处理和流输入上执行,而无需修改Table程序本身。在这两种情况下,如果流记录没有延迟,程序会产生相同的结果(详细信息请参阅流概念)。
2. 操作
Table API支持以下操作。请注意,并不是所有的操作都可以批处理和流处理。它们被相应地贴上了标签。
2.1 Scan, Projection, and Filter (扫描、投影和过滤)
From
类似于SQL查询中的FROM
子句。对已注册表执行扫描。
java
Table orders = tableEnv.from("Orders");
FromValues
类似于SQL查询中的VALUES
子句。从提供的行生成内联表。
您可以使用row(...)
表达式来创建复合行:
java
Table table = tEnv.fromValues(
row(1, "ABC"),
row(2L, "ABCDE")
);
将生成一个表,其模式如下:
java
root
|-- f0: BIGINT NOT NULL // original types INT and BIGINT are generalized to BIGINT
|-- f1: VARCHAR(5) NOT NULL // original types CHAR(3) and CHAR(5) are generalized
// to VARCHAR(5). VARCHAR is used instead of CHAR so that
// no padding is applied
该方法将自动从输入表达式派生类型。如果某个位置的类型不同,该方法将尝试为所有类型找到一个共同的超类型。如果公共超类型不存在,则会抛出异常。
您还可以显式指定所请求的类型。它可能有助于分配更通用的类型,例如DECIMAL
或命名列。
java
Table table = tEnv.fromValues(
DataTypes.ROW(
DataTypes.FIELD("id", DataTypes.DECIMAL(10, 2)),
DataTypes.FIELD("name", DataTypes.STRING())
),
row(1, "ABC"),
row(2L, "ABCDE")
);
将生成一个具有以下模式的表:
java
root
|-- id: DECIMAL(10, 2)
|-- name: STRING
Select
类似于SQL SELECT
语句。执行选择操作。
java
Table orders = tableEnv.from("Orders");
Table result = orders.select($("a"), $("c").as("d"));
您可以使用星号(*
)作为通配符,选择表中的所有列。
As
重命名字段。
java
Table orders = tableEnv.from("Orders");
Table result = orders.as("x, y, z, t");
Where / Filter
类似于SQL的WHERE
子句。过滤掉未通过筛选谓词的行。
java
Table orders = tableEnv.from("Orders");
Table result = orders.where($("b").isEqual("red"));
或者:
java
Table orders = tableEnv.from("Orders");
Table result = orders.filter($("b").isEqual("red"));
2.2 Column Operations
AddColumns
执行字段添加操作。如果添加的字段已经存在,它将抛出异常。
java
Table orders = tableEnv.from("Orders");
Table result = orders.addColumns(concat($("c"), "sunny"));
AddOrReplaceColumns
执行字段添加操作。如果添加的列名与现有列名相同,则将替换现有字段。此外,如果添加的字段具有重复的字段名,则使用最后一个字段名。
java
Table orders = tableEnv.from("Orders");
Table result = orders.addOrReplaceColumns(concat($("c"), "sunny").as("desc"));
DropColumns
java
Table orders = tableEnv.from("Orders");
Table result = orders.dropColumns($("b"), $("c"));
RenameColumns
执行字段重命名操作。字段表达式应该是别名表达式,只有现有的字段可以重命名。
java
Table orders = tableEnv.from("Orders");
Table result = orders.renameColumns($("b").as("b2"), $("c").as("c2"));
2.3 Aggregations
GroupBy Aggregation
类似于SQL GROUP BY子
句。使用以下正在运行的聚合操作符对分组键上的行进行分组,以分组方式对行进行聚合。
java
Table orders = tableEnv.from("Orders");
Table result = orders.groupBy($("a")).select($("a"), $("b").sum().as("d"));
对于流查询,根据聚合类型和不同分组键的数量,计算查询结果所需的状态可能无限增长。请提供空闲状态保留时间,以防止状态大小过大。具体请参见空闲状态保留时间。
GroupBy Window Aggregation
在组窗口和可能的一个或多个分组键上对表进行分组和聚合。
java
Table orders = tableEnv.from("Orders");
Table result = orders
.window(Tumble.over(lit(5).minutes()).on($("rowtime")).as("w")) // define window
.groupBy($("a"), $("w")) // group by key and window
// access window properties and aggregate
.select(
$("a"),
$("w").start(),
$("w").end(),
$("w").rowtime(),
$("b").sum().as("d")
);
Over Window Aggregation
类似于SQL OVER
子句。基于前一行和后一行的窗口(范围),计算每行的窗口聚合。有关更多细节,请参阅over window部分。
java
Table orders = tableEnv.from("Orders");
Table result = orders
// define window
.window(
Over
.partitionBy($("a"))
.orderBy($("rowtime"))
.preceding(UNBOUNDED_RANGE)
.following(CURRENT_RANGE)
.as("w"))
// sliding aggregate
.select(
$("a"),
$("b").avg().over($("w")),
$("b").max().over($("w")),
$("b").min().over($("w"))
);
所有聚合必须在相同的窗口上定义,即相同的分区、排序和范围。目前,只支持前边(UNBOUNDED
和有界)到CURRENT ROW
范围的窗口。目前还不支持带有FOLLOWING
的范围。ORDER BY
必须在单个时间属性上指定。
Distinct Aggregation
类似于SQL的DISTINCT
聚合子句,例如COUNT(DISTINCT a)
。DISTINCT
聚合声明聚合函数(内置的或用户定义的)只应用于不同的输入值。可应用于GroupBy
聚合、GroupBy
窗口聚合和Over
窗口聚合。
java
Table orders = tableEnv.from("Orders");
// Distinct aggregation on group by
Table groupByDistinctResult = orders
.groupBy($("a"))
.select($("a"), $("b").sum().distinct().as("d"));
// Distinct aggregation on time window group by
Table groupByWindowDistinctResult = orders
.window(Tumble
.over(lit(5).minutes())
.on($("rowtime"))
.as("w")
)
.groupBy($("a"), $("w"))
.select($("a"), $("b").sum().distinct().as("d"));
// Distinct aggregation on over window
Table 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修饰符。
java
Table orders = tEnv.from("Orders");
// Use distinct aggregation for user-defined aggregate functions
tEnv.registerFunction("myUdagg", new MyUdagg());
orders.groupBy("users")
.select(
$("users"),
call("myUdagg", $("points")).distinct().as("myDistinctResult")
);
对于流查询,根据聚合类型和不同分组键的数量,计算查询结果所需的状态可能无限增长。请提供空闲状态保留时间,以防止状态大小过大。具体请参见空闲状态保留时间。
Distinct
类似于SQL的DISTINCT
子句。返回具有不同值组合的记录。
java
Table orders = tableEnv.from("Orders");
Table result = orders.distinct();
2.4 Joins
Inner Join
类似于SQL JOIN
子句。连接两个表。两个表必须具有不同的字段名,并且必须通过连接操作符或使用where或filter 操作符定义至少一个相等连接谓词。
java
Table left = tableEnv.from("MyTable").select($("a"), $("b"), $("c"));
Table right = tableEnv.from("MyTable").select($("d"), $("e"), $("f"));
Table result = left.join(right)
.where($("a").isEqual($("d")))
.select($("a"), $("b"), $("e"));
Outer Join
类似于SQL的LEFT/RIGHT/FULL OUTER JOIN
子句。连接两个表。两个表必须具有不同的字段名,并且必须定义至少一个相等连接谓词。
java
Table left = tableEnv.from("MyTable").select($("a"), $("b"), $("c"));
Table right = tableEnv.from("MyTable").select($("d"), $("e"), $("f"));
Table leftOuterResult = left.leftOuterJoin(right, $("a").isEqual($("d")))
.select($("a"), $("b"), $("e"));
Table rightOuterResult = left.rightOuterJoin(right, $("a").isEqual($("d")))
.select($("a"), $("b"), $("e"));
Table fullOuterResult = left.fullOuterJoin(right, $("a").isEqual($("d")))
.select($("a"), $("b"), $("e"));
Interval Join
间隔连接是常规连接的一个子集,可以以流方式处理。
间隔连接至少需要一个相等连接谓词和一个限制两边时间的连接条件 。这样的条件可以通过两个适当的范围谓词(<
、<=
、>=
、>
)或单个相等谓词来定义,该谓词比较两个输入表中相同类型的时间属性(即处理时间或事件时间)。
java
Table left = tableEnv.from("MyTable").select($("a"), $("b"), $("c"), $("ltime"));
Table right = tableEnv.from("MyTable").select($("d"), $("e"), $("f"), $("rtime"));
Table result = left.join(right)
.where(
and(
$("a").isEqual($("d")),
$("ltime").isGreaterOrEqual($("rtime").minus(lit(5).minutes())),
$("ltime").isLess($("rtime").plus(lit(10).minutes()))
))
.select($("a"), $("b"), $("e"), $("ltime"));
使用表函数的内部连接(UDTF)
用表函数的结果连接表。左表(外部表)的每一行都与表函数的相应调用所产生的所有行连接。如果表函数调用返回空结果,则删除左(外部)表的一行。
java
// register User-Defined Table Function
TableFunction<Tuple3<String,String,String>> split = new MySplitUDTF();
tableEnv.registerFunction("split", split);
// join
Table orders = tableEnv.from("Orders");
Table result = orders
.joinLateral(call("split", $("c")).as("s", "t", "v"))
.select($("a"), $("b"), $("s"), $("t"), $("v"));
Left Outer Join with Table Function (UDTF)
用表函数的结果连接表。左表(外部表)的每一行都与表函数的相应调用所产生的所有行连接。如果表函数调用返回空结果,则保留相应的外部行,并用空值填充结果。
java
// register User-Defined Table Function
TableFunction<Tuple3<String,String,String>> split = new MySplitUDTF();
tableEnv.registerFunction("split", split);
// join
Table orders = tableEnv.from("Orders");
Table result = orders
.leftOuterJoinLateral(call("split", $("c")).as("s", "t", "v"))
.select($("a"), $("b"), $("s"), $("t"), $("v"));
Join with Temporal Table
临时表是跟踪随时间变化的表。
临时表函数提供对临时表在特定时间点的状态的访问。使用临时表函数连接表的语法与使用表函数进行内部连接中的语法相同。
目前只支持与临时表的内部连接。
java
Table ratesHistory = tableEnv.from("RatesHistory");
// register temporal table function with a time attribute and primary key
TemporalTableFunction rates = ratesHistory.createTemporalTableFunction(
"r_proctime",
"r_currency");
tableEnv.registerFunction("rates", rates);
// join with "Orders" based on the time attribute and key
Table orders = tableEnv.from("Orders");
Table result = orders
.joinLateral(call("rates", $("o_proctime")), $("o_currency").isEqual($("r_currency")));
2.5 Set Operations(集合操作)
Union
类似于SQL UNION
子句。联合删除重复记录的两个表。两个表必须具有相同的字段类型。
java
Table left = tableEnv.from("orders1");
Table right = tableEnv.from("orders2");
left.union(right);
UnionAll
类似于SQL的UNION ALL
子句。合并两个表。两个表必须具有相同的字段类型。
java
Table left = tableEnv.from("orders1");
Table right = tableEnv.from("orders2");
left.unionAll(right);
Intersect
类似于SQL的INTERSECT
子句。Intersect返回两个表中都存在的记录。如果一条记录在一个或两个表中不止一次出现,则只返回一次,即结果表中没有重复的记录。两个表必须具有相同的字段类型。
java
Table left = tableEnv.from("orders1");
Table right = tableEnv.from("orders2");
left.intersect(right);
IntersectAll
类似于SQL的INTERSECT ALL
子句。IntersectAll返回两个表中都存在的记录。如果一条记录在两个表中都出现了不止一次,那么返回的次数和它在两个表中出现的次数一样多,也就是说,结果表可能有重复的记录。两个表必须具有相同的字段类型。
java
Table left = tableEnv.from("orders1");
Table right = tableEnv.from("orders2");
left.intersectAll(right);
Minus
类似于SQL的EXCEPT
子句。Minus 返回左表中不存在右表的记录。左表中的重复记录只返回一次,即删除重复记录。两个表必须具有相同的字段类型。
java
Table left = tableEnv.from("orders1");
Table right = tableEnv.from("orders2");
left.minus(right);
MinusAll
类似于SQL的EXCEPT ALL
子句。MinusAll返回不存在于右表中的记录。在左表中出现n次,在右表中出现m次的记录将返回(n - m)次,即删除与右表中出现的相同数量的重复项。两个表必须具有相同的字段类型。
java
Table left = tableEnv.from("orders1");
Table right = tableEnv.from("orders2");
left.minusAll(right);
In
类似于SQL的IN
子句。如果表达式存在于给定的表子查询中,则IN
返回true。子查询表必须由一列组成。此列必须具有与表达式相同的数据类型。
java
Table left = tableEnv.from("Orders1")
Table right = tableEnv.from("Orders2");
Table result = left.select($("a"), $("b"), $("c")).where($("a").in(right));
2.6 OrderBy, Offset & Fetch
Order By
类似于SQL的ORDER BY
子句。返回跨所有并行分区全局排序的记录。对于无界表,此操作需要对时间属性进行排序或随后的获取操作。
java
Table result = tab.orderBy($("a").asc());
Offset & Fetch
类似于SQL的OFFSET
和FETCH
子句。OFFSET
操作限制来自偏移位置的(可能是排序的)结果。FETCH
操作将结果(可能是排序的)限制在前n行。通常,这两个操作之前都有一个排序操作符。对于无界表,OFFSET
操作需要一个FETCH
操作。
java
// returns the first 5 records from the sorted result
Table result1 = in.orderBy($("a").asc()).fetch(5);
// skips the first 3 records and returns all following records from the sorted result
Table result2 = in.orderBy($("a").asc()).offset(3);
// skips the first 10 records and returns the next 5 records from the sorted result
Table result3 = in.orderBy($("a").asc()).offset(10).fetch(5);
2.7 Insert
与SQL查询中的INSERT INTO
子句类似,该方法执行对已注册输出表的插入操作。insertInto()
方法将把INSERT INTO
转换为TablePipeline
。管道可以用TablePipeline.explain()
解释,用TablePipeline.execute()
执行。
输出表必须在TableEnvironment
中注册(请参阅连接器表)。此外,注册表的模式必须与查询的模式匹配。
java
Table orders = tableEnv.from("Orders");
orders.insertInto("OutOrders").execute();
2.8 Group Windows
组窗口根据时间或行计数间隔将组行聚合到有限组中,并对每个组计算一次聚合函数。对于批处理表,窗口是按时间间隔对记录进行分组的方便快捷方式。
Windows是使用window(GroupWindow w)
子句定义的,并且需要使用as子句指定别名。为了按窗口对表进行分组,必须在groupBy(...)
子句中像引用常规分组属性一样引用窗口别名。下面的示例展示了如何在表上定义窗口聚合。
java
Table table = input
.window([GroupWindow w].as("w")) // define window with alias w
.groupBy($("w")) // group the table by window w
.select($("b").sum()); // aggregate
在流环境中,窗口聚合只能在对一个或多个属性进行分组的情况下并行计算,例如,groupBy(...)
子句引用了一个窗口别名和至少一个附加属性。只引用窗口别名的groupBy(...)
子句(如上面的示例)只能由单个非并行任务求值。下面的示例展示了如何定义具有附加分组属性的窗口聚合。
java
Table table = input
.window([GroupWindow w].as("w")) // define window with alias w
.groupBy($("w"), $("a")) // group the table by attribute a and window w
.select($("a"), $("b").sum()); // aggregate
窗口属性,如时间窗口的开始、结束或rowtime
时间戳,可以作为窗口别名的属性添加到select语句中,分别为w.start
、w.end
和w.rowtime
。窗口开始和行时间戳 是包含上下窗口边界的时间戳。相反,窗口结束时间戳是唯一的上窗口边界。例如,从下午2点开始的30分钟滚动窗口将以14:00:00.000作为开始时间戳,14:29:59.999作为行时间戳,14:30:00.000作为结束时间戳。
java
Table table = input
.window([GroupWindow w].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
参数定义如何将行映射到窗口。Window
不是用户可以实现的接口。相反,Table API提供了一组预定义的具有特定语义的Window
类。下面列出了支持的窗口定义。
Tumble (Tumbling Windows)
滚动窗口(tumbling window
)将行分配给固定长度的非重叠连续窗口。例如,5分钟的滚动窗口以5分钟的间隔对行进行分组。滚动窗口可以根据事件时间、处理时间或行计数来定义
通过使用Tumble
类定义滚动窗口,如下所示:
Method | Description |
---|---|
over | Defines the length the window, either as time or row-count interval. |
on | The time attribute to group (time interval) or sort (row count) on. For batch queries this might be any Long or Timestamp attribute. For streaming queries this must be a declared event-time or processing-time time attribute. |
as | 为窗口分配别名。别名用于在下面的groupBy() 子句中引用窗口,并可选地在select() 子句中选择窗口属性,如窗口开始、结束或行时间戳。 |
java
// Tumbling Event-time Window
.window(Tumble.over(lit(10).minutes()).on($("rowtime")).as("w"));
// Tumbling Processing-time Window (assuming a processing-time attribute "proctime")
.window(Tumble.over(lit(10).minutes()).on($("proctime")).as("w"));
// Tumbling Row-count Window (assuming a processing-time attribute "proctime")
.window(Tumble.over(rowInterval(10)).on($("proctime")).as("w"));
Slide (Sliding Windows)
滑动窗口(sliding window)
具有固定的大小,并按指定的滑动间隔滑动。如果滑动间隔小于窗口大小,则滑动窗口重叠。因此,可以将行分配给多个窗口。例如,15分钟大小的滑动窗口和5分钟的滑动间隔将每行分配给3个不同的15分钟大小的窗口,这些窗口以5分钟的间隔进行评估。滑动窗口可以根据事件时间、处理时间或行计数来定义。
滑动窗口是使用Slide
类定义的,如下所示:
Method | Description |
---|---|
over | Defines the length of the window, either as time or row-count interval. |
every | Defines the slide interval, either as time or row-count interval. The slide interval must be of the same type as the size interval. |
on | The time attribute to group (time interval) or sort (row count) on. For batch queries this might be any Long or Timestamp attribute. For streaming queries this must be a declared event-time or processing-time time attribute. |
as | Assigns an alias to the window. The alias is used to reference the window in the following groupBy() clause and optionally to select window properties such as window start, end, |
Session (Session Windows)
会话窗口(Session windows
)没有固定的大小,但它们的边界是由不活动的时间间隔定义的,即,如果在定义的间隙期间没有事件出现,会话窗口将关闭。例如,当在30分钟不活动后观察到一行时(否则该行将被添加到现有窗口中),具有30分钟间隙的会话窗口开始,如果在30分钟内没有添加行,则关闭会话窗口。会话窗口可以在事件时间或处理时间上工作。
会话窗口通过使用Session
类定义如下:
Method | Description |
---|---|
withGap | Defines the gap between two windows as time interval. |
on | The time attribute to group (time interval) or sort (row count) on. For batch queries this might be any Long or Timestamp attribute. For streaming queries this must be a declared event-time or processing-time time attribute. |
as | Assigns an alias to the window. The alias is used to reference the window in the following groupBy() clause and optionally to select window properties such as window start, end, or rowtime timestamps in the select() clause. |
java
// Session Event-time Window
.window(Session.withGap(lit(10).minutes()).on($("rowtime")).as("w"));
// Session Processing-time Window (assuming a processing-time attribute "proctime")
.window(Session.withGap(lit(10).minutes()).on($("proctime")).as("w"));
2.9 Over Windows
over 窗口聚合就像在标准SQL (OVER
子句) ,并在查询的SELECT
子句中定义。与在GROUP BY
子句中指定的组窗口不同,over窗口不会折叠行。相反,通过窗口聚合为相邻行的范围内的每个输入行计算聚合。
Over windows使用 window(w: OverWindow*)
子句(在Python API中使用over_window(*OverWindow)
定义,并通过select()
方法中的别名引用。下面的示例展示了如何在表上定义跨窗口聚合。
java
Table table = input
.window([OverWindow w].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窗口定义作为over(和其他类)上的方法公开,并列出如下:
Partition By (Optional)
在一个或多个属性上定义输入的分区。每个分区单独排序,聚合函数分别应用于每个分区。
注意:在流环境中,如果窗口包含partition by子句,则只能并行计算跨窗口聚合。如果没有
partitionBy(...)
,流将由单个非并行任务处理。
Order By (Required)
定义每个分区内的行顺序,从而定义聚合函数应用于行的顺序。
注意:对于流查询,这必须是声明的事件时间或处理时间时间属性。目前,只支持一个sort属性。
Preceding (Optional)
定义包含在窗口中且位于当前行之前的行间隔。间隔可以指定为时间或行计数间隔。
有边界的over 窗口用间隔的大小来指定,例如,10。分钟表示时间间隔或10。行计数间隔的行数。
无边界的over 窗口是使用常量指定的,例如,UNBOUNDED_RANGE
用于时间间隔,UNBOUNDED_ROW
用于行计数间隔。无界窗口从分区的第一行开始。
如果省略前一个子句,则使用UNBOUNDED_RANGE
和CURRENT_RANGE
作为窗口的默认preceding 、following 两个参数。
Following (Optional)
定义包含在窗口中并在当前行之后的行的窗口间隔。间隔必须以与preceding 间隔相同的单位(time或row-count)指定。
目前,不支持在当前行之后的行的over窗口。相反,你可以指定以下两个常量之一:
- CURRENT_ROW将窗口的上界设置为当前行。
- CURRENT_RANGE将窗口的上界设置为当前行的排序键,即,与当前行具有相同排序键的所有行都包含在窗口中。
如果省略以下子句,则时间间隔窗口的上界定义为CURRENT_RANGE
,行计数间隔窗口的上界定义为CURRENT_ROW
。
As (Required)
Unbounded Over Windows
java
// 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
java
// Bounded Event-time over window (assuming an event-time attribute "rowtime")
.window(Over.partitionBy($("a")).orderBy($("rowtime")).preceding(lit(1).minutes()).as("w"));
// Bounded Processing-time over window (assuming a processing-time attribute "proctime")
.window(Over.partitionBy($("a")).orderBy($("proctime")).preceding(lit(1).minutes()).as("w"));
// Bounded Event-time Row-count over window (assuming an event-time attribute "rowtime")
.window(Over.partitionBy($("a")).orderBy($("rowtime")).preceding(rowInterval(10)).as("w"));
// Bounded Processing-time Row-count over window (assuming a processing-time attribute "proctime")
.window(Over.partitionBy($("a")).orderBy($("proctime")).preceding(rowInterval(10)).as("w"));
2.10 基于行的操作
基于行的操作生成具有多列的输出。
Map
使用自定义标量函数或内置标量函数执行映射操作。如果输出类型是复合类型,则输出将被平面化。
java
public class MyMapFunction extends ScalarFunction {
public Row eval(String a) {
return Row.of(a, "pre-" + a);
}
@Override
public TypeInformation<?> getResultType(Class<?>[] signature) {
return Types.ROW(Types.STRING, Types.STRING);
}
}
ScalarFunction func = new MyMapFunction();
tableEnv.registerFunction("func", func);
Table table = input
.map(call("func", $("c"))).as("a", "b");
FlatMap
使用表函数执行flatMap操作。
java
public class MyFlatMapFunction extends TableFunction<Row> {
public void eval(String str) {
if (str.contains("#")) {
String[] array = str.split("#");
for (int i = 0; i < array.length; ++i) {
collect(Row.of(array[i], array[i].length()));
}
}
}
@Override
public TypeInformation<Row> getResultType() {
return Types.ROW(Types.STRING, Types.INT);
}
}
TableFunction func = new MyFlatMapFunction();
tableEnv.registerFunction("func", func);
Table table = input
.flatMap(call("func", $("c"))).as("a", "b");
Aggregate
使用聚合函数执行聚合操作。您必须使用select语句关闭"aggregate",而select语句不支持聚合函数。如果输出类型是复合类型,则aggregate的输出将被平面化。
java
public class MyMinMaxAcc {
public int min = 0;
public int max = 0;
}
public class MyMinMax extends AggregateFunction<Row, MyMinMaxAcc> {
public void accumulate(MyMinMaxAcc acc, int value) {
if (value < acc.min) {
acc.min = value;
}
if (value > acc.max) {
acc.max = value;
}
}
@Override
public MyMinMaxAcc createAccumulator() {
return new MyMinMaxAcc();
}
public void resetAccumulator(MyMinMaxAcc acc) {
acc.min = 0;
acc.max = 0;
}
@Override
public Row getValue(MyMinMaxAcc acc) {
return Row.of(acc.min, acc.max);
}
@Override
public TypeInformation<Row> getResultType() {
return new RowTypeInfo(Types.INT, Types.INT);
}
}
AggregateFunction myAggFunc = new MyMinMax();
tableEnv.registerFunction("myAggFunc", myAggFunc);
Table table = input
.groupBy($("key"))
.aggregate(call("myAggFunc", $("a")).as("x", "y"))
.select($("key"), $("x"), $("y"));
Group Window Aggregate
在组窗口和可能的一个或多个分组键上对表进行分组和聚合。您必须使用select语句关闭"aggregate"。而且select语句不支持"*"或聚合函数。
java
AggregateFunction myAggFunc = new MyMinMax();
tableEnv.registerFunction("myAggFunc", myAggFunc);
Table table = input
.window(Tumble.over(lit(5).minutes())
.on($("rowtime"))
.as("w")) // define window
.groupBy($("key"), $("w")) // group by key and window
.aggregate(call("myAggFunc", $("a")).as("x", "y"))
.select($("key"), $("x"), $("y"), $("w").start(), $("w").end()); // access window properties and aggregate results
FlatAggregate
类似于GroupBy聚合。使用以下正在运行的表聚合操作符对分组键上的行进行分组,以分组方式对行进行聚合。与AggregateFunction的不同之处在于,TableAggregateFunction可以为一个组返回0条或多条记录。您必须使用select语句关闭"flatAggregate"。而且select语句不支持聚合函数。
除了使用emitValue
输出结果,还可以使用emitUpdateWithRetract
方法。与emitValue
不同,emitUpdateWithRetract
用于发出已更新的值。此方法以收回模式增量输出数据,即,一旦有更新,我们必须在发送新的更新记录之前收回旧记录。如果在表聚合函数中定义了emitUpdateWithRetract
方法,则优先使用emitUpdateWithRetract
方法,因为该方法被认为比emitValue
方法更有效,因为它可以增量输出值。
java
/**
* Accumulator for Top2.
*/
public class Top2Accum {
public Integer first;
public Integer second;
}
/**
* The top2 user-defined table aggregate function.
*/
public class Top2 extends TableAggregateFunction<Tuple2<Integer, Integer>, Top2Accum> {
@Override
public Top2Accum createAccumulator() {
Top2Accum acc = new Top2Accum();
acc.first = Integer.MIN_VALUE;
acc.second = Integer.MIN_VALUE;
return acc;
}
public void accumulate(Top2Accum acc, Integer v) {
if (v > acc.first) {
acc.second = acc.first;
acc.first = v;
} else if (v > acc.second) {
acc.second = v;
}
}
public void merge(Top2Accum acc, java.lang.Iterable<Top2Accum> iterable) {
for (Top2Accum otherAcc : iterable) {
accumulate(acc, otherAcc.first);
accumulate(acc, otherAcc.second);
}
}
public void emitValue(Top2Accum acc, Collector<Tuple2<Integer, Integer>> out) {
// emit the value and rank
if (acc.first != Integer.MIN_VALUE) {
out.collect(Tuple2.of(acc.first, 1));
}
if (acc.second != Integer.MIN_VALUE) {
out.collect(Tuple2.of(acc.second, 2));
}
}
}
tEnv.registerFunction("top2", new Top2());
Table orders = tableEnv.from("Orders");
Table result = orders
.groupBy($("key"))
.flatAggregate(call("top2", $("a")).as("v", "rank"))
.select($("key"), $("v"), $("rank");
3、Data Types
请参阅有关数据类型的专用页面。
泛型类型和(嵌套的)复合类型(例如POJOs、元组、行、Scala case类)也可以是一行的字段。
可以使用值访问函数访问具有任意嵌套的组合类型的字段。
泛型类型被视为黑盒,可以由用户定义函数传递或处理。