80-flink-tableapi-filnksql:
Flink-Table API 和 Flink SQL简介 | 新老版本Flink批流处理对比 | 读取文件和Kafka消费数据 | API 和 SQL查询表:https://blog.csdn.net/qq_40180229/article/details/106457648
flink-Table&sql-碰到的几个问题记录:https://blog.csdn.net/weixin_41956627/article/details/110050094
使用样例
导入pom依赖,1.11.X之后,推荐使用blink版本
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-table-planner-blink_${scala.binary.version}artifactId>
<version>${flink.version}version>
dependency>
java样例代码
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest1_Example.java
Txt文件:sensor.txt
输出结果
result> sensor_1,35.8
sql> sensor_1,35.8
result> sensor_1,36.3
sql> sensor_1,36.3
result> sensor_1,32.8
sql> sensor_1,32.8
result> sensor_1,37.1
sql> sensor_1,37.1
Table API和SQL的程序结构,与流式处理的程序结构十分类似
StreamTableEnvironment tableEnv = ... // 创建表的执行环境
// 创建一张表,用于读取数据
tableEnv.connect(...).createTemporaryTable("inputTable");
// 注册一张表,用于把计算结果输出
tableEnv.connect(...).createTemporaryTable("outputTable");
// 通过 Table API 查询算子,得到一张结果表
Table result = tableEnv.from("inputTable").select(...);
// 通过SQL查询语句,得到一张结果表
Table sqlResult = tableEnv.sqlQuery("SELECT ... FROM inputTable ...");
// 将结果表写入输出表中
result.insertInto("outputTable");
新版本blink,真正把批处理、流处理都以DataStream实现。
创建环境-样例代码
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest2_CommonApi.java
TableEnvironment可以调用connect()
方法,连接外部系统,并调用.createTemporaryTable()
方法,在Catalog中注册表
tableEnv
.connect(...) // 定义表的数据来源,和外部系统建立连接
.withFormat(...) // 定义数据格式化方法
.withSchema(...) // 定义表结构
.createTemporaryTable("MyTable"); // 创建临时表
创建表的执行环境,需要将flink流处理的执行环境传入
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
TableEnvironment是flink中集成Table API和SQL的核心概念,所有对表的操作都基于TableEnvironment
测试代码
pom依赖
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-table-planner-blink_${scala.binary.version}artifactId>
<version>${flink.version}version>
dependency>
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-csvartifactId>
<version>${flink.version}version>
dependency>
java代码
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest2_CommonApi.java
输入文件
sensor.txt
输出
root
|-- id: STRING
|-- timestamp: BIGINT
|-- temp: DOUBLE
sensor_1,1547718199,35.8
sensor_6,1547718201,15.4
sensor_7,1547718202,6.7
sensor_10,1547718205,38.1
sensor_1,1547718207,36.3
sensor_1,1547718209,32.8
sensor_1,1547718212,37.1
Table API是集成在Scala和Java语言内的查询API
Table API基于代表"表"的Table类,并提供一整套操作处理的方法API;这些方法会返回一个新的Table对象,表示对输入表应用转换操作的结果
有些关系型转换操作,可以由多个方法调用组成,构成链式调用结构
Table sensorTable = tableEnv.from("inputTable");
Table resultTable = sensorTable
.select("id","temperature")
.filter("id = 'sensor_1'");
java代码
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest2_CommonApi.java
输出结果
里面的false表示上一条保存的记录被删除,true则是新加入的数据
所以Flink的Table API在更新数据时,实际是先删除原本的数据,再添加新数据。
result> sensor_6,15.4
sqlagg> (true,sensor_1,1,35.8)
sqlagg> (true,sensor_6,1,15.4)
sqlagg> (true,sensor_7,1,6.7)
sqlagg> (true,sensor_10,1,38.1)
agg> (true,sensor_1,1,35.8)
agg> (true,sensor_6,1,15.4)
sqlagg> (false,sensor_1,1,35.8)
sqlagg> (true,sensor_1,2,36.05)
agg> (true,sensor_7,1,6.7)
sqlagg> (false,sensor_1,2,36.05)
sqlagg> (true,sensor_1,3,34.96666666666666)
agg> (true,sensor_10,1,38.1)
sqlagg> (false,sensor_1,3,34.96666666666666)
sqlagg> (true,sensor_1,4,35.5)
agg> (false,sensor_1,1,35.8)
agg> (true,sensor_1,2,36.05)
agg> (false,sensor_1,2,36.05)
agg> (true,sensor_1,3,34.96666666666666)
agg> (false,sensor_1,3,34.96666666666666)
agg> (true,sensor_1,4,35.5)
flink Sql 1.11 executeSql报No operators defined in streaming topology. Cannot generate StreamGraph.:https://blog.csdn.net/qq_26502245/article/details/107376528
写入到文件有局限,只能是批处理,且只能是追加写,不能是更新式的随机写。
java代码
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest3_FileOutput.java
输出结果(输出到out.txt文件)
sensor_6,15.4
这个程序只能运行一次,再运行一次报错
Kafka作为消息队列,和文件系统类似的,只能往里追加数据,不能修改数据。
测试代码
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest4_KafkaPipeLine.java
启动kafka目录里自带的zookeeper
$ bin/zookeeper-server-start.sh config/zookeeper.properties
启动kafka服务
$ bin/kafka-server-start.sh config/server.properties
新建kafka生产者和消费者
$ bin/kafka-console-producer.sh --broker-list localhost:9092 --topic sensor
$ bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic sinktest
启动Flink程序,在kafka生产者console中输入数据,查看输出
输入(kafka-console-producer)
>sensor_1,1547718199,35.8
>sensor_6,1547718201,15.4
>sensor_7,1547718202,6.7
>sensor_10,1547718205,38.1
>sensor_6,1547718209,34.5
输出(kafka-console-consumer)
代码中,只筛选id为sensor_6
的,所以输出没有问题
sensor_6,15.4
sensor_6,34.5
可以创建Table来描述ES中的数据,作为输出的TableSink
tableEnv.connect(
new Elasticsearch()
.version("6")
.host("localhost",9200,"http")
.index("sensor")
.documentType("temp")
)
.inUpsertMode()
.withFormat(new Json())
.withSchema(new Schema()
.field("id",DataTypes.STRING())
.field("count",DataTypes.BIGINT())
)
.createTemporaryTable("esOutputTable");
aggResultTable.insertInto("esOutputTable");
需要的pom依赖
Flink专门为Table API的jdbc连接提供了flink-jdbc连接器,需要先引入依赖
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-jdbc_2.12artifactId>
<version>1.10.3version>
<scope>providedscope>
dependency>
可以创建Table来描述MySql中的数据,作为输入和输出
String sinkDDL =
"create table jdbcOutputTable (" +
" id varchar(20) not null, " +
" cnt bigint not null " +
") with (" +
" 'connector.type' = 'jdbc', " +
" 'connector.url' = 'jdbc:mysql://localhost:3306/test', " +
" 'connector.table' = 'sensor_count', " +
" 'connector.driver' = 'com.mysql.jdbc.Driver', " +
" 'connector.username' = 'root', " +
" 'connector.password' = '123456' )";
tableEnv.sqlUpdate(sinkDDL); // 执行DDL创建表
aggResultSqlTable.insertInto("jdbcOutputTable");
Flink- 将表转换成DataStream | 查看执行计划 | 流处理和关系代数的区别 | 动态表 | 流式持续查询的过程 | 将流转换成动态表 | 持续查询 | 将动态表转换成 DS:https://blog.csdn.net/qq_40180229/article/details/106479537
表可以转换为 DataStream 或 DataSet ,这样自定义流处理或批处理程序就可以继续在 Table API 或 SQL 查询的结果上运行了
将表转换为 DataStream 或 DataSet 时,需要指定生成的数据类型,即要将表的每一行转换成的数据类型
表作为流式查询的结果,是动态更新的
转换有两种转换模式:追加(Appende)模式和撤回(Retract)模式
追加模式
DataStream<Row> resultStream = tableEnv.toAppendStream(resultTable,Row.class);
撤回模式
用于任何场景。有些类似于更新模式中Retract模式,它只有Insert和Delete两类操作。
得到的数据会增加一个Boolean类型的标识位(返回的第一个字段),用它来表示到底是新增的数据(Insert),还是被删除的数据(Delete)。
(更新数据,会先删除旧数据,再插入新数据)
对于一个DataStream,可以直接转换成Table,进而方便地调用Table API做转换操作
DataStream<SensorReading> dataStream = ...
Table sensorTable = tableEnv.fromDataStream(dataStream);
默认转换后的Table schema和DataStream中的字段定义一一对应,也可以单独指定出来
DataStream<SensorReading> dataStream = ...
Table sensorTable = tableEnv.fromDataStream(dataStream,
"id, timestamp as ts, temperature");
基于DataStream创建临时视图
tableEnv.createTemporaryView("sensorView",dataStream);
tableEnv.createTemporaryView("sensorView",
dataStream, "id, timestamp as ts, temperature");
基于Table创建临时视图
tableEnv.createTemporaryView("sensorView", sensorTable);
Table API 提供了一种机制来解释计算表的逻辑和优化查询计划
查看执行计划,可以通过TableEnvironment.explain(table)
方法或TableEnvironment.explain()
方法完成,返回一个字符串,描述三个计划
String explaination = tableEnv.explain(resultTable);
System.out.println(explaination);
Flink- 将表转换成DataStream | 查看执行计划 | 流处理和关系代数的区别 | 动态表 | 流式持续查询的过程 | 将流转换成动态表 | 持续查询 | 将动态表转换成 DS :https://blog.csdn.net/qq_40180229/article/details/106479537
Table API和SQL,本质上还是基于关系型表的操作方式;而关系型表、关系代数,以及SQL本身,一般是有界的,更适合批处理的场景。这就导致在进行流处理的过程中,理解会稍微复杂一些,需要引入一些特殊概念。
其实关系代数(主要就是指关系型数据库中的表)和SQL,主要就是针对批处理的,这和流处理有天生的隔阂。
关系代数(表)/SQL | 流处理 | |
---|---|---|
处理的数据对象 | 字段元组的有界集合 | 字段元组的无限序列 |
查询(Query)对数据的访问 | 可以访问到完整的数据输入 | 无法访问所有数据,必须持续"等待"流式输入 |
查询终止条件 | 生成固定大小的结果集后终止 | 永不停止,根据持续收到的数据不断更新查询结果 |
随着新数据的到来,不停地在之前的基础上更新结果。这样得到的表,在Flink Table API概念里,就叫做“动态表”(Dynamic Tables)。
流式表查询的处理过程:
来一条数据插入一条数据
持续查询,会在动态表上做计算处理,并作为结果生成新的动态表。
与批处理查询不同,连续查询从不终止,并根据输入表上的更新更新其结果表。
在任何时间点,连续查询的结果在语义上,等同于在输入表的快照上,以批处理模式执行的同一查询的结果。
下图为一个点击事件流的持续查询,是一个分组聚合做count统计的查询。
仅追加(Append-only)流
撤回(Retract)流
撤回流是包含两类消息的流:添加(Add)消息和撤回(Retract)消息
动态表通过将INSERT 编码为add消息、DELETE 编码为retract消息、UPDATE编码为被更改行(前一行)的retract消息和更新后行(新行)的add消息,转换为retract流。
Upsert(更新插入流)
Upsert流也包含两种类型的消息:Upsert消息和删除(Delete)消息
通过将INSERT和UPDATE更改编码为upsert消息,将DELETE更改编码为DELETE消息,就可以将具有唯一键(Unique Key)的动态表转换为流。
在定义 Table Schema 期间,可以使用.proctime
,指定字段名定义处理时间字段
这个proctime属性只能通过附加逻辑字段,来扩展物理schema。因此,只能在schema定义的末尾定义它
Table sensorTable = tableEnv.fromDataStream(dataStream,"id, temperature, pt.proctime");
.withSchema(new Schema()
.field("id", DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.field("temperature",DataTypes.DOUBLE())
.field("pt",DataTypes.TIMESTAMP(3))
.proctime()
)
String sinkDDL =
"create table dataTable (" +
" id varchar(20) not null, " +
" ts bigint, " +
" temperature double, " +
" pt AS PROCTIME() " +
" ) with (" +
" 'connector.type' = 'filesystem', " +
" 'connector.path' = '/sensor.txt', " +
" 'format.type' = 'csv')";
tableEnv.sqlUpdate(sinkDDL);
测试代码
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest5_TimeAndWindow.java
输出如下:
root
|-- id: STRING
|-- ts: BIGINT
|-- temp: DOUBLE
|-- pt: TIMESTAMP(3) *PROCTIME*
sensor_1,1547718199,35.8,2021-02-03T16:50:58.048
sensor_6,1547718201,15.4,2021-02-03T16:50:58.048
sensor_7,1547718202,6.7,2021-02-03T16:50:58.050
sensor_10,1547718205,38.1,2021-02-03T16:50:58.050
sensor_1,1547718207,36.3,2021-02-03T16:50:58.051
sensor_1,1547718209,32.8,2021-02-03T16:50:58.051
sensor_1,1547718212,37.1,2021-02-03T16:50:58.051
由DataStream转换成表时指定(推荐)
在DataStream转换成Table,使用.rowtime
可以定义事件事件属性
// 将DataStream转换为Table,并指定时间字段
Table sensorTable = tableEnv.fromDataStream(dataStream,
"id, timestamp.rowtime, temperature");
// 或者,直接追加时间字段
Table sensorTable = tableEnv.fromDataStream(dataStream,
"id, temperature, timestamp, rt.rowtime");
.withSchema(new Schema()
.field("id", DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.rowtime(
new Rowtime()
.timestampsFromField("timestamp") // 从字段中提取时间戳
.watermarksPeriodicBounded(1000) // watermark延迟1秒
)
.field("temperature",DataTypes.DOUBLE())
)
String sinkDDL =
"create table dataTable (" +
" id varchar(20) not null, " +
" ts bigint, " +
" temperature double, " +
" rt AS TO_TIMESTAMP( FROM_UNIXTIME(ts) ), " +
" watermark for rt as rt - interval '1' second"
" ) with (" +
" 'connector.type' = 'filesystem', " +
" 'connector.path' = '/sensor.txt', " +
" 'format.type' = 'csv')";
tableEnv.sqlUpdate(sinkDDL);
测试代码
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest5_TimeAndWindow.java
输出如下:
注:这里最后一列rt里显示的是EventTime,而不是Processing Time
root
|-- id: STRING
|-- ts: BIGINT
|-- temp: DOUBLE
|-- rt: TIMESTAMP(3) *ROWTIME*
sensor_1,1547718199,35.8,2019-01-17T09:43:19
sensor_6,1547718201,15.4,2019-01-17T09:43:21
sensor_7,1547718202,6.7,2019-01-17T09:43:22
sensor_10,1547718205,38.1,2019-01-17T09:43:25
sensor_1,1547718207,36.3,2019-01-17T09:43:27
sensor_1,1547718209,32.8,2019-01-17T09:43:29
sensor_1,1547718212,37.1,2019-01-17T09:43:32
Flink-分组窗口 | Over Windows | SQL 中的 Group Windows | SQL 中的 Over Windows:https://blog.csdn.net/qq_40180229/article/details/106482095
Group Windows 是使用 window(w:GroupWindow)子句定义的,并且必须由as子句指定一个别名。
为了按窗口对表进行分组,窗口的别名必须在 group by 子句中,像常规的分组字段一样引用
Table table = input
.window([w:GroupWindow] as "w") // 定义窗口,别名为w
.groupBy("w, a") // 按照字段 a和窗口 w分组
.select("a,b.sum"); // 聚合
Table API 提供了一组具有特定语义的预定义 Window 类,这些类会被转换为底层 DataStream 或 DataSet 的窗口操作
分组窗口分为三种:
// Tumbling Event-time Window(事件时间字段rowtime)
.window(Tumble.over("10.minutes").on("rowtime").as("w"))
// Tumbling Processing-time Window(处理时间字段proctime)
.window(Tumble.over("10.minutes").on("proctime").as("w"))
// Tumbling Row-count Window (类似于计数窗口,按处理时间排序,10行一组)
.window(Tumble.over("10.rows").on("proctime").as("w"))
// Sliding Event-time Window
.window(Slide.over("10.minutes").every("5.minutes").on("rowtime").as("w"))
// Sliding Processing-time window
.window(Slide.over("10.minutes").every("5.minutes").on("proctime").as("w"))
// Sliding Row-count window
.window(Slide.over("10.rows").every("5.rows").on("proctime").as("w"))
// Session Event-time Window
.window(Session.withGap("10.minutes").on("rowtime").as("w"))
// Session Processing-time Window
.window(Session.withGap("10.minutes").on("proctime").as("w"))
Group Windows定义在SQL查询的Group By子句中
测试代码
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest5_TimeAndWindow.java
输出:
root
|-- id: STRING
|-- ts: BIGINT
|-- temp: DOUBLE
|-- rt: TIMESTAMP(3) *ROWTIME*
result> sensor_1,1,35.8,2019-01-17T09:43:20
result> sensor_6,1,15.4,2019-01-17T09:43:30
result> sensor_1,2,34.55,2019-01-17T09:43:30
result> sensor_10,1,38.1,2019-01-17T09:43:30
result> sensor_7,1,6.7,2019-01-17T09:43:30
sql> (true,sensor_1,1,35.8,2019-01-17T09:43:20)
result> sensor_1,1,37.1,2019-01-17T09:43:40
sql> (true,sensor_6,1,15.4,2019-01-17T09:43:30)
sql> (true,sensor_1,2,34.55,2019-01-17T09:43:30)
sql> (true,sensor_10,1,38.1,2019-01-17T09:43:30)
sql> (true,sensor_7,1,6.7,2019-01-17T09:43:30)
sql> (true,sensor_1,1,37.1,2019-01-17T09:43:40)
SQL中over的用法:https://blog.csdn.net/liuyuehui110/article/details/42736667
sql over的作用及用法:https://www.cnblogs.com/xiayang/articles/1886372.html
Over window 聚合是标准 SQL 中已有的(over 子句),可以在查询的 SELECT 子句中定义
Over window 聚合,会针对每个输入行,计算相邻行范围内的聚合
Over windows 使用 window(w:overwindows*)子句定义,并在 select()方法中通过别名来引用
Table table = input
.window([w: OverWindow] as "w")
.select("a, b.sum over w, c.min over w");
Table API 提供了 Over 类,来配置 Over 窗口的属性
// 无界的事件时间over window (时间字段 "rowtime")
.window(Over.partitionBy("a").orderBy("rowtime").preceding(UNBOUNDED_RANGE).as("w"))
//无界的处理时间over window (时间字段"proctime")
.window(Over.partitionBy("a").orderBy("proctime").preceding(UNBOUNDED_RANGE).as("w"))
// 无界的事件时间Row-count over window (时间字段 "rowtime")
.window(Over.partitionBy("a").orderBy("rowtime").preceding(UNBOUNDED_ROW).as("w"))
//无界的处理时间Row-count over window (时间字段 "rowtime")
.window(Over.partitionBy("a").orderBy("proctime").preceding(UNBOUNDED_ROW).as("w"))
partitionBy是可选项
// 有界的事件时间over window (时间字段 "rowtime",之前1分钟)
.window(Over.partitionBy("a").orderBy("rowtime").preceding("1.minutes").as("w"))
// 有界的处理时间over window (时间字段 "rowtime",之前1分钟)
.window(Over.partitionBy("a").orderBy("porctime").preceding("1.minutes").as("w"))
// 有界的事件时间Row-count over window (时间字段 "rowtime",之前10行)
.window(Over.partitionBy("a").orderBy("rowtime").preceding("10.rows").as("w"))
// 有界的处理时间Row-count over window (时间字段 "rowtime",之前10行)
.window(Over.partitionBy("a").orderBy("proctime").preceding("10.rows").as("w"))
SELECT COUNT(amount) OVER (
PARTITION BY user
ORDER BY proctime
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
FROM Orders
// 也可以做多个聚合
SELECT COUNT(amount) OVER w, SUM(amount) OVER w
FROM Orders
WINDOW w AS (
PARTITION BY user
ORDER BY proctime
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
测试代码
java代码
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest5_TimeAndWindow.java
输出:
因为partition by id order by rt rows between 2 preceding and current row
,所以最后2次关于sensor_1
的输出的count(id)
都是3,但是计算出来的平均值不一样。(前者计算倒数3条sensor_1的数据,后者计算最后最新的3条sensor_1数据的平均值)
result> sensor_1,2019-01-17T09:43:19,1,35.8
sql> (true,sensor_1,2019-01-17T09:43:19,1,35.8)
result> sensor_6,2019-01-17T09:43:21,1,15.4
sql> (true,sensor_6,2019-01-17T09:43:21,1,15.4)
result> sensor_7,2019-01-17T09:43:22,1,6.7
sql> (true,sensor_7,2019-01-17T09:43:22,1,6.7)
result> sensor_10,2019-01-17T09:43:25,1,38.1
sql> (true,sensor_10,2019-01-17T09:43:25,1,38.1)
result> sensor_1,2019-01-17T09:43:27,2,36.05
sql> (true,sensor_1,2019-01-17T09:43:27,2,36.05)
sql> (true,sensor_1,2019-01-17T09:43:29,3,34.96666666666666)
result> sensor_1,2019-01-17T09:43:29,3,34.96666666666666
result> sensor_1,2019-01-17T09:43:32,3,35.4
sql> (true,sensor_1,2019-01-17T09:43:32,3,35.4)
Flink-函数 | 用户自定义函数(UDF)标量函数 | 表函数 | 聚合函数 | 表聚合函数:https://blog.csdn.net/qq_40180229/article/details/106482550
用户定义函数(User-defined Functions,UDF)是一个重要的特性,它们显著地扩展了查询的表达能力
一些系统内置函数无法解决的需求,我们可以用UDF来自定义实现
在大多数情况下,用户定义的函数必须先注册,然后才能在查询中使用
函数通过调用 registerFunction()
方法在 TableEnvironment 中注册。当用户定义的函数被注册时,它被插入到 TableEnvironment 的函数目录中,这样Table API 或 SQL 解析器就可以识别并正确地解释它
Scalar Funcion类似于map,一对一
Table Function类似flatMap,一对多
用户定义的标量函数,可以将0、1或多个标量值,映射到新的标量值
为了定义标量函数,必须在 org.apache.flink.table.functions 中扩展基类Scalar Function,并实现(一个或多个)求值(eval)方法
标量函数的行为由求值方法决定,求值方法必须public公开声明并命名为 eval
public static class HashCode extends ScalarFunction {
private int factor = 13;
public HashCode(int factor) {
this.factor = factor;
}
public int eval(String id) {
return id.hashCode() * 13;
}
}
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/udf/UdfTest1_ScalarFunction.java
输出结果
sensor_1,1547718199,-772373508
sensor_1,1547718199,-772373508
sensor_6,1547718201,-772373443
sensor_6,1547718201,-772373443
sensor_7,1547718202,-772373430
sensor_7,1547718202,-772373430
sensor_10,1547718205,1826225652
sensor_10,1547718205,1826225652
sensor_1,1547718207,-772373508
sensor_1,1547718207,-772373508
sensor_1,1547718209,-772373508
sensor_1,1547718209,-772373508
sensor_1,1547718212,-772373508
sensor_1,1547718212,-772373508
Scalar Funcion类似于map,一对一
Table Function类似flatMap,一对多
用户定义的表函数,也可以将0、1或多个标量值作为输入参数;与标量函数不同的是,它可以返回任意数量的行作为输出,而不是单个值
为了定义一个表函数,必须扩展 org.apache.flink.table.functions 中的基类 TableFunction 并实现(一个或多个)求值方法
表函数的行为由其求值方法决定,求值方法必须是 public 的,并命名为 eval
public static class Split extends TableFunction<Tuple2<String, Integer>> {
// 定义属性,分隔符
private String separator = ",";
public Split(String separator) {
this.separator = separator;
}
public void eval(String str) {
for (String s : str.split(separator)) {
collect(new Tuple2<>(s, s.length()));
}
}
}
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/udf/UdfTest2_TableFunction.java
输出结果
result> sensor_1,1547718199,sensor,6
result> sensor_1,1547718199,1,1
sql> sensor_1,1547718199,sensor,6
sql> sensor_1,1547718199,1,1
result> sensor_6,1547718201,sensor,6
result> sensor_6,1547718201,6,1
sql> sensor_6,1547718201,sensor,6
sql> sensor_6,1547718201,6,1
result> sensor_7,1547718202,sensor,6
result> sensor_7,1547718202,7,1
sql> sensor_7,1547718202,sensor,6
sql> sensor_7,1547718202,7,1
result> sensor_10,1547718205,sensor,6
result> sensor_10,1547718205,10,2
sql> sensor_10,1547718205,sensor,6
sql> sensor_10,1547718205,10,2
result> sensor_1,1547718207,sensor,6
result> sensor_1,1547718207,1,1
sql> sensor_1,1547718207,sensor,6
sql> sensor_1,1547718207,1,1
result> sensor_1,1547718209,sensor,6
result> sensor_1,1547718209,1,1
sql> sensor_1,1547718209,sensor,6
sql> sensor_1,1547718209,1,1
result> sensor_1,1547718212,sensor,6
result> sensor_1,1547718212,1,1
sql> sensor_1,1547718212,sensor,6
sql> sensor_1,1547718212,1,1
聚合,多对一,类似前面的窗口聚合
createAccumulator()
accumulate()
getValue()
createAccumulator()
方法创建空累加器accumulate()
方法来更新累加器getValue()
方法来计算并返回最终结果src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/udf/UdfTest3_AggregateFunction.java
输出结果:
result> (true,sensor_1,35.8)
result> (true,sensor_6,15.4)
result> (true,sensor_7,6.7)
result> (true,sensor_10,38.1)
result> (false,sensor_1,35.8)
result> (true,sensor_1,36.05)
sql> (true,sensor_1,35.8)
result> (false,sensor_1,36.05)
sql> (true,sensor_6,15.4)
result> (true,sensor_1,34.96666666666666)
sql> (true,sensor_7,6.7)
result> (false,sensor_1,34.96666666666666)
sql> (true,sensor_10,38.1)
result> (true,sensor_1,35.5)
sql> (false,sensor_1,35.8)
sql> (true,sensor_1,36.05)
sql> (false,sensor_1,36.05)
sql> (true,sensor_1,34.96666666666666)
sql> (false,sensor_1,34.96666666666666)
sql> (true,sensor_1,35.5)
createAccumulator()
accumulate()
emitValue()
createAccumulator()
方法可以创建空累加器。accumulate()
方法来更新累加器。emitValue()
方法来计算并返回最终结果。Flink-函数 | 用户自定义函数(UDF)标量函数 | 表函数 | 聚合函数 | 表聚合函数:https://blog.csdn.net/qq_40180229/article/details/106482550
主来源::https://space.bilibili.com/302417610?spm_id_from=333.337.0.0
参考:https://ashiamd.github.io/docsify-notes/#/study/BigData/Flink/%E5%B0%9A%E7%A1%85%E8%B0%B7Flink%E5%85%A5%E9%97%A8%E5%88%B0%E5%AE%9E%E6%88%98-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0