文章目录
- Flink中Table API & SQL简单介绍
- 两个规划者之间的主要差异
- TableEnvironmetnt认识
- 表API和SQL程序的结构
- 创建一个TableEnvironment
- 开发环境构建
- TableEnvironment基本操作
- 内部CataLog注册
- 内部Table注册
- TableSource注册
- 表API
- Scan, Projection, and Filter
- Column Operations
- Aggregations
- Joins
- Set Operations
- OrderBy, Offset & Fetch
- Insert
- Group Windows
- Tumbling Window(滚动窗口)
- Sliding Window(滑动窗口)
- Session Window(会话窗口)
- Over Window
- Unbounded Over Windows
- Bounded Over Windows
- Row-based Operations(基于行操作)
- 数据类型
Flink中Table API & SQL简单介绍
对于像DataFrame
这样的关系型编程接口,因其强大且灵活的表达能力,能够让用户通过非常丰富的接口对数据进行处理,有效降低了用户的使用成本,近年来逐渐成为主流大数据处理框架主要接口形式之一。Flink也提供了关系型编程接口Table API
以及基于TableAPI
的SQL API
,让用户能够通过使用结构化编程接口高效的构建Flink应用。同时Table API
以及SQL
能够统一处理批量和实时计算业务,无需切换到修改任何应用代码就能够基于同一套API编写流式应用和批量应用,从而达到真正意义的批流统一。
Apache Flink
具有两个关系API
- 表API
和SQL
- 用于统一流和批处理。Table API
是Scala
和Java
的语言集成查询API
,允许以非常直观的方式组合来自关系运算符的查询,Table API
和SQL
接口彼此紧密集成,以及Flink
的DataStream
和DataSet API
。您可以轻松地在基于API
构建的所有API和库之间切换。例如,您可以使用CEP
库从DataStream
中提取模式,然后使用Table API
分析模式,或者可以在预处理上运行Gelly
图算法之前使用SQL查询扫描,过滤和聚合批处理表数据。
两个规划者之间的主要差异
Blink
将批处理作业视为流式传输的特例。因此,也不支持Table和DataSet
之间的转换,批处理作业不会转换为Dataset
程序,而是转化为DataStream
程序,与流作业相同。Blink planner
不支持BatchTableSource
,使用有界StreamTableSource
而不是它。Blink
规划器仅支持全新Catalog
,不支持ExternalCatalog
已弃用。- 旧计划程序和Blink计划程序的
FilterableTableSource
的实现是不兼容的。 旧计划者将PlannerExpressions
推向FilterableTableSource
,而Blink计划者将推下表达式。- 基于字符串的键值配置选项(有关详细信息,请参阅有关配置的文档)仅用于Blink规划器。
PlannerConfig
在两个规划者中的实现(CalciteConfig
)是不同的。- Blink规划器将多个接收器优化为一个DAG(仅在
TableEnvironment
上支持,而不在StreamTableEnvironment
上支持)。 旧规划器将始终将每个接收器优化为新的DAG,其中所有DAG彼此独立。- 现在,旧规划器不支持目录统计,而Blink规划器则支持。
TableEnvironmetnt认识
和DataStream
一样,Table API
和SQL
中具有相同的基本编程模型。首先需要构建对应的TableEnvironment
创建关系型编程环境,才能够在程序中使用Table API
和SQL
来编写程序,另外Table API
和SQL
接口可以在应用中同时使用。Flink SQL
基于Apache Cacite
框架实现SQL
协议,是构建在Table API
之上的更高级接口。
表API和SQL程序的结构
// JAVA
TableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
// register a Table
tableEnv.registerTable("table1", ...) // or
tableEnv.registerTableSource("table2", ...); // or
tableEnv.registerExternalCatalog("extCat", ...);
// register an output Table
tableEnv.registerTableSink("outputTable", ...);
// create a Table from a Table API query
Table tapiResult = tableEnv.scan("table1").select(...);
// create a Table from a SQL query
Table sqlResult = tableEnv.sqlQuery("SELECT ... FROM table2 ... ");
// emit a Table API result Table to a TableSink, same for SQL result
tapiResult.insertInto("outputTable");
// execute
tableEnv.execute("java_job");
创建一个TableEnvironment
这TableEnvironment
是Table API
和SQL
集成的核心概念,他主要负责:
- 在
Table
内部目录中注册表 - 注册一个外部表
- 执行
SQL
查询 - 注册用户行为的(标量,表或聚合)函数
- 将
DataStream
或者DataSet
转换为Table
- 持有对
ExecutionEnvironment
或的引用StreamExecutionEnvironment
表始终绑定到特定的TableEnvironment
。 不可能在同一查询中组合不同TableEnvironments
的表,例如,加入或联合它们。
通过使用StreamExecutionEnvironment
或ExecutionEnvironment
和可选的TableConfig
调用静态BatchTableEnvironment.create()
或StreamTableEnvironment.create()
方法来创建TableEnvironment
。 TableConfig
可用于配置TableEnvironment
或自定义查询优化和转换过程.
开发环境构建
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-core</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--Flink Table API / sql-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-api-java-bridge_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-planner_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-common</artifactId>
<version>${flink.version}</version>
</dependency>
Note 1 : 由于Flink Table接口中引入了Apache Calcite第三方库,会阻止Java虚拟机对用户的Classloaders进行垃圾回收,因此不建议用户在构建Flink应用时将flink table依赖包打包进fat-jar中,可以在集群环境中将{FLINK_HOME}/opt的对应的flink-table jar复制到{FLINK_HOME}/lib中解决此类问题。
Note 2 : If there is only one planner jar in /lib directory, you can use useAnyPlanner (use_any_planner for python) to create specific EnvironmentSettings.
TableEnvironment基本操作
使用Table API & SQL
创建Flink
应用程序,需要在环境中创建TableEnvironment
对象,TableEnvironment
提供了注册内部表,执行Flink SQL
语句、注册自定义函数等功能。
// 批处理环境
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// 获取表操作环境对象
BatchTableEnvironment tableEnvironment = BatchTableEnvironment.create(env);
// 流处理环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 获取表操作环境对象
StreamTableEnvironment tableEnvironment = StreamTableEnvironment.create(env);
内部CataLog注册
内部Table注册
在获取TableEnvironment
对象后,可以使用TableEnvironment
提供的方法来注册相应的数据源和数据表信息。所有对数据库和数据表的元数据信息存放在Flink CataLog
内部目录中,其存放了Flink
内部所有与Table
相关的元数据信息,包括表的结构信息,数据源信息等。
Table nameSumDeptid = tableEnvironment.scan("emp").select("name,deptid,email");
// 将 nameSumDeptid 在CataLog中注册成内部表 nameTable
tableEnvironment.registerTable("nameTable" , nameSumDeptid)
TableSource注册
在使用Table API
,可以将外部数据源直接注册成Table
数据结构。
// 批处理环境
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// 获取表操作环境对象
BatchTableEnvironment tableEnvironment = BatchTableEnvironment.create(env);
// 创建csv文件类型的TableSource
TableSource csvSource = new CsvTable("/file.csv" , ",");
// 将创建好的TableSource注册到BatchTableEnvironment
tableEnvironment.registerTableSource("CsvTable" , csvSource);
表API
扫描表Orders
,并按照a
字段进行分组聚合,查找a
和b
的数量并将b
的计数取名为cnt
Table orders = tEnv.scan("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();
下一个示例显示了一个更复杂的Table API程序。程序再次扫描Orders
表格。它过滤空值,规范化a
String类型的字段,并计算每小时和产品a
的平均计费金额b
。
Table orders = tEnv.scan("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");
Scan, Projection, and Filter
Operators | Description |
Scan | 与SQL查询中的FROM子句类似。 执行已注册表的扫描.
|
Select | 与SQL SELECT语句类似。 执行选择操作。
You can use star (
|
As | Renames fields. |
Where / Filter | 与SQL WHERE子句类似。 过滤掉未通过过滤谓词的行.
|
Column Operations
Operators | Description |
AddColumns | 执行字段添加操作。 如果添加的字段已存在,它将抛出异常。
|
AddOrReplaceColumns | 执行字段添加操作。 如果添加列名称与现有列名称相同,则将替换现有字段。 此外,如果添加的字段具有重复的字段名称,则使用最后一个字段。
|
DropColumns | 执行字段放置操作。 字段表达式应该是字段引用表达式,并且只能删除现有字段.
|
RenameColumns | 执行字段重命名操作。 字段表达式应该是别名表达式,并且只能重命名现有字段。
|
Aggregations
Operators | Description |
GroupBy Aggregation | 与SQL GROUP BY子句类似。使用以下运行的聚合运算符对分组键上的行进行分组,以按组聚合行。
Note: 对于流式查询,计算查询结果所需的状态可能会无限增长,具体取决于聚合类型和不同分组键的数量。请提供具有有效保留间隔的查询配置,以防止状态过大 |
GroupBy Window Aggregation | 组和聚合组窗口上的表以及可能的一个或多个分组键。 |
Over Window AggregationStreaming | 类似于SQL OVER子句。基于前一行和后一行的窗口(范围)计算每行的窗口聚合 Note: 必须在同一窗口中定义所有聚合,即相同的分区,排序和范围。目前,仅支持具有PRREDING(UNBOUNDED和有界)到CURRENT ROW范围的窗口。尚不支持使用FOLLOWING的范围。必须在单个时间属性上指定ORDER BY 。 |
Distinct Aggregation | 类似于SQL DISTINCT聚合子句,例如COUNT(DISTINCT a)。不同聚合声明聚合函数(内置或用户定义)仅应用于不同的输入值。Distinct可以应用于GroupBy聚合,GroupBy窗口聚合和Over Window Aggregation。 Note: 用户定义的聚合函数也可以与DISTINCT修饰符一起使用。要仅为不同的值计算聚合结果,只需将distinct修饰符添加到聚合函数即可。 [外链图片转存失败 Note: 对于流式查询,计算查询结果所需的状态可能会无限增长,具体取决于不同字段的数量。请提供具有有效保留间隔的查询配置,以防止状态过大。 |
Distinct | 与SQL DISTINCT子句类似。返回具有不同值组合的记录。 [外链图片转存失败 对于流式查询,计算查询结果所需的状态可能会无限增长,具体取决于不同字段的数量。请提供具有有效保留间隔的查询配置,以防止状态过大。如果启用了状态清除,则distinct必须发出消息以防止下游运营商的过早状态驱逐,这使得distinct包含结果更新. |
Joins
Operators | Description |
Inner Join | 与SQL JOIN子句类似。加入两张桌子。两个表必须具有不同的字段名称,并且必须通过连接运算符或使用where或filter运算符定义至少一个相等连接谓词。 **Note ?*对于流式查询,计算查询结果所需的状态可能会无限增长,具体取决于不同输入行的数量。请提供具有有效保留间隔的查询配置,以防止状态过大 |
Outer Join | 与SQL LEFT / RIGHT / FULL OUTER JOIN子句类似。加入两张桌子。两个表必须具有不同的字段名称,并且必须至少定义一个相等连接谓词。 |
Time-windowed Join | 时间窗口连接是可以以流方式处理的常规连接的子集。 时间窗口连接需要至少一个等连接谓词和一个限制双方时间的连接条件。这样的条件可以由两个适当的范围谓词( |
Inner Join with Table Function | 使用表函数的结果连接表。 左(外)表的每一行与表函数的相应调用产生的所有行连接。 如果其表函数调用返回空结果,则删除左(外)表的一行。 |
Left Outer Join with Table Function | 使用表函数的结果连接表。 左(外)表的每一行与表函数的相应调用产生的所有行连接。 如果表函数调用返回空结果,则保留相应的外部行,并使用空值填充结果。 |
Join with Temporal Table | 时态表是跟踪随时间变化的表。 时态表函数提供对特定时间点的时态表的状态的访问。 使用时态表函数连接表的语法与使用表函数的内部连接相同。 目前仅支持具有时态表的内部联接。 |
Set Operations
Operators | Description |
Union | 与SQL UNION子句类似。 联合两个表删除了重复记录。 两个表必须具有相同的字段类型。 |
UnionAll | 类似于SQL UNION ALL子句。 工会两张桌子。 两个表必须具有相同的字段类型。 |
Intersect | 类似于SQL INTERSECT子句。 Intersect返回两个表中存在的记录。 如果一个或两个表不止一次出现记录,则只返回一次,即结果表没有重复记录。 两个表必须具有相同的字段类型。 |
IntersectAll | 类似于SQL INTERSECT ALL子句。 IntersectAll返回两个表中存在的记录。 如果两个表中的记录多次出现,则返回的次数与两个表中的记录一样多,即结果表可能具有重复记录。 两个表必须具有相同的字段类型。 |
Minus | 与SQL EXCEPT子句类似。 减号返回左表中右表中不存在的记录。 左表中的重复记录只返回一次,即删除重复项。 两个表必须具有相同的字段类型。 |
MinusAll | 类似于SQL EXCEPT ALL子句。 MinusAll返回右表中不存在的记录。 在左表中出现n次并在右表中出现m次的记录返回(n-m)次,即,删除右表中出现的重复数。 两个表必须具有相同的字段类型。 |
In | 与SQL IN子句类似。 如果表达式存在于给定的表子查询中,则返回true。 子查询表必须包含一列。 此列必须与表达式具有相同的数据类型。 |
OrderBy, Offset & Fetch
Operators | Description |
Order By | 与SQL ORDER BY子句类似。 返回跨所有并行分区全局排序的记录。 |
Offset & Fetch | 类似于SQL OFFSET和FETCH子句。 偏移和提取限制从排序结果返回的记录数。 Offset和Fetch在技术上是Order By运算符的一部分,因此必须以它为前缀。 |
Insert
Operators | Description |
Insert Into | 类似于SQL查询中的INSERT INTO子句。 执行插入已注册的输出表。 |
Group Windows
Group Window
和DataStream API
、DataSet API
中提供的窗口一致,都是将流式数据集根据窗口;类型切分为有界数据集,然后在有界数据集上进行聚合类运算。
tableEnv.scan("Sensors")
.winwods([w : Window] as "window") // 指定窗口类型,并命名为window
.groupBy("window") // 根据窗口进行聚合,窗口数据会分配到单个Task算子中
.select("varl1.sum") // 指定对var字段进行Sum求和
在流式计算中,GroupBy
聚合条件可以以上实例选择Window名称,也可以是一个或多个Key
值与Window
的组合。
- 如果指定Window名称,则和
Global Window
相似,窗口中的数据都会被汇总到一个Task线程中处理,统计窗口全局的结果; - 如果指定
Key
和Window
名称组合,则窗口中的数据分布到并行计算的算子实例中去计算结果。
tableEnv.scan("Sensors")
.window([w:Window] as "window") //
.groupBy("window" , "id") // 根据窗口聚合,窗口数据分配到每单个Task算子
.select("id" , "var1.sum") // 指定val字段求和
在select
语句中,我们除了可以获取到数据元素以外,还可以获取到窗口的元数据信息。
tableEnv.scan("Sensors")
.window([w:Window] as "window") //
.groupBy("window" , "id") // 根据窗口聚合,窗口数据分配到每单个Task算子
.select("id" , "var1.sum","window.start","window.end","window.rowtime") // 指定val字段求和
**Note : ** 在以上window()
方法中需要指定的是不同的窗口类型,已确定数据元素被分配到窗口的逻辑。在Table API
中支持Tumble , Sliding , Session Window
三种窗口类型,并分别通过不同的Window
对象来完成定义。
Tumbling Window(滚动窗口)
前面提到滚动窗口的窗口长度是固定的,窗口之间的数据不会重合。滚动窗口可以基于Evenet Time
、Process Time
以及Row-Count
来定义。如下实例:Table API
中滚动窗口使用Tumble Class
来创建,且分别基于Evenet Time
、Process Time
以及Row-Count
来定义窗口。
// 通过scan方法在CataLog中查询Sensors表
tableEnv.scan("Sensors")
// 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"));
-
over
: 指定窗口的长度 -
on
: 定义了窗口基于的时间概念类型为EventTime
还是ProcessTime
,EventTime
对应着rowtime
,ProcessTime
对应着proctime
-
as
: 将创建的窗口重命名,同时窗口名称需要在后续的孙子中使用。
Sliding Window(滑动窗口)
滑动窗口的长度也是固定的,但窗口与窗口之间的数据能够重合。滑动窗口可以基于Evenet Time
、Process Time
以及Row-Count
来定义。如下实例:Table API
中滑动窗口使用Slide Class
来创建,且分别基于Evenet Time
、Process Time
以及Row-Count
来定义窗口。
// 通过scan方法在CataLog中查询Sensors表
tableEnv.scan("Sensors")
// 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"));
-
over
: 定义窗口的长度,可以是时间或行计数间隔。 -
every
: 定义滑动间隔,可以是时间间隔也可以是行数。滑动间隔必须与大小间隔的类型相同。 -
on
: 定义了窗口基于的时间概念类型为EventTime
还是ProcessTime
,EventTime
对应着rowtime
,ProcessTime
对应着proctime
-
as
: 将创建的窗口重命名,同时窗口名称需要在后续的孙子中使用。
Session Window(会话窗口)
与Tumbling
、Sliding
窗口不同的是,Session
窗口不需要指定固定的窗口时间,而是通过判断固定时间内数据的活跃性来切分窗口。例如 10 min内数据不接入则切分窗口并触发计算。Session
窗口只能基于EventTime
和ProcessTime
时间概念来定义,通过withGrap
操作符指定数据不活跃的时间Grap
,表示超过该时间数据不接入,则切分窗口并触发计算。
// 通过scan方法在CataLog中查询Sensors表
tableEnv.scan("Sensors")
// 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 Window
Over Window
和标准SQL
中提供的Over
语法功能类似,也是一种数据聚合计算的方式,但和Group Window
不同的是,Over Window
不需要对输入数据按照窗口大小进行堆叠。Over Window
是基于当前数据和其周围邻近范围内数据进行聚合统计的,例如基于当前记录前面的20条数据,然后基于这些数据统计某一指标的聚合结果。
在Table API
中,Over Window
也是在window方法中指定,但后面不需要和groupBy
操作符绑定,后面直接接SELECT
操作符,并在select
操作符中指定需要查询字段和聚合指标。
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
方法 | 需要 | 描述 |
| 可 选的 | 定义一个或多个属性上的输入分区。每个分区都是单独排序的,聚合函数分别应用于每个分区。 **注意:**在流式环境中,如果窗口包含partition by子句,则只能并行计算窗口聚合。没有 |
| 需要 | 定义每个分区中行的顺序,从而定义聚合函数应用于行的顺序。 **注意:**对于流式查询,这必须是声明的事件时间或处理时间属性。目前,仅支持单个排序属性。 |
| 可选的 | 定义窗口中包含的行的间隔,并在当前行之前。间隔可以指定为时间或行计数间隔。在窗口上限定具有间隔的大小,例如, |
| 可选的 | 定义窗口中包含的行的窗口间隔,并跟随当前行。必须在与前一个间隔(时间或行计数)相同的单位中指定间隔。目前,不支持在当前行之后包含行的窗口。相反,您可以指定两个常量之一: |
| 需要 | 为覆盖窗口指定别名。别名用于引用以下 |
Note : 目前,同一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(基于行操作)
The row-based operations generate outputs with multiple columns.
Operators | Describtion |
Map | 使用用户定义的标量函数或内置标量函数执行映射操作。如果输出类型是复合类型,则输出将被展平。 |
FlatMap | |
Aggregate | |
FlatAggregate | |
Group Window FlatAggregate |
数据类型