80-flink-tableapi-filnksql:
Table API和Flink SQL
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
1 概述
- Flink 对批处理和流处理,提供了统一的上层 API
- Table API 是一套内嵌在 Java 和 Scala 语言中的查询API,它允许以非常直观的方式组合来自一些关系运算符的查询
- Flink 的 SQL 支持基于实现了 SQL 标准的 Apache Calcite
使用样例
-
导入pom依赖,1.11.X之后,推荐使用blink版本
<!-- Table API 和 Flink SQL --> <dependency> <groupId>org.apache.flink</groupId> <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
2 基本程序结构
-
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");
3 Table API批处理和流处理
新版本blink,真正把批处理、流处理都以DataStream实现。
创建环境-样例代码
src/main/java/com/zh/flink/s02_flink_api/s07_tableapi/TableTest2_CommonApi.java
3.1 表(Table)
- TableEnvironment可以注册目录Catalog,并可以基于Catalog注册表
- 表(Table)是由一个"标示符"(identifier)来指定的,由3部分组成:Catalog名、数据库(database)名和对象名
- 表可以是常规的,也可以是虚拟的(视图,View)
- 常规表(Table)一般可以用来描述外部数据,比如文件、数据库表或消息队列的数据,也可以直接从DataStream转换而来
- 视图(View)可以从现有的表中创建,通常是table API或者SQL查询的一个结果集
3.2 创建表
-
TableEnvironment可以调用
connect()
方法,连接外部系统,并调用.createTemporaryTable()
方法,在Catalog中注册表tableEnv .connect(...) // 定义表的数据来源,和外部系统建立连接 .withFormat(...) // 定义数据格式化方法 .withSchema(...) // 定义表结构 .createTemporaryTable("MyTable"); // 创建临时表
3.3 创建TableEnvironment
-
创建表的执行环境,需要将flink流处理的执行环境传入
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
-
TableEnvironment是flink中集成Table API和SQL的核心概念,所有对表的操作都基于TableEnvironment
- 注册Catalog
- 在Catalog中注册表
- 执行SQL查询
- 注册用户自定义函数(UDF)
测试代码
-
pom依赖
<!-- Table API 和 Flink SQL --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-table-planner-blink_${scala.binary.version}</artifactId> <version>${flink.version}</version> </dependency> <!-- csv --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-csv</artifactId> <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
3.4 表的查询
-
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
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
-
3.5 更新模式
- 对于流式查询,需要声明如何在表和外部连接器之间执行转换
- 与外部系统交换的消息类型,由更新模式(Uadate Mode)指定
- 追加(Append)模式
- 表只做插入操作,和外部连接器只交换插入(Insert)消息
- 撤回(Retract)模式
- 表和外部连接器交换添加(Add)和撤回(Retract)消息
- 插入操作(Insert)编码为Add消息;删除(Delete)编码为Retract消息;更新(Update)编码为上一条的Retract和下一条的Add消息
- 更新插入(Upsert)模式
- 更新和插入都被编码为Upsert消息;删除编码为Delete消息
3.6 输出到ES
-
可以创建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");
3.7 输出到MySQL
-
需要的pom依赖
Flink专门为Table API的jdbc连接提供了flink-jdbc连接器,需要先引入依赖
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-jdbc --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-jdbc_2.12</artifactId> <version>1.10.3</version> <scope>provided</scope> </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");
4 表和流的转换
Flink- 将表转换成DataStream | 查看执行计划 | 流处理和关系代数的区别 | 动态表 | 流式持续查询的过程 | 将流转换成动态表 | 持续查询 | 将动态表转换成 DS:https://blog.csdn.net/qq_40180229/article/details/106479537
4.1 将Table转换成DataStream
-
表可以转换为 DataStream 或 DataSet ,这样自定义流处理或批处理程序就可以继续在 Table API 或 SQL 查询的结果上运行了
-
将表转换为 DataStream 或 DataSet 时,需要指定生成的数据类型,即要将表的每一行转换成的数据类型
-
表作为流式查询的结果,是动态更新的
-
转换有两种转换模式:追加(Appende)模式和撤回(Retract)模式
-
追加模式
- 用于表只会被插入(Insert)操作更改的场景
DataStream<Row> resultStream = tableEnv.toAppendStream(resultTable,Row.class);
-
撤回模式
-
用于任何场景。有些类似于更新模式中Retract模式,它只有Insert和Delete两类操作。
-
得到的数据会增加一个Boolean类型的标识位(返回的第一个字段),用它来表示到底是新增的数据(Insert),还是被删除的数据(Delete)。
(更新数据,会先删除旧数据,再插入新数据)
-
4.2 将DataStream转换成表
-
对于一个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");
4.3 创建临时视图(Temporary View)
-
基于DataStream创建临时视图
tableEnv.createTemporaryView("sensorView",dataStream); tableEnv.createTemporaryView("sensorView", dataStream, "id, timestamp as ts, temperature");
-
基于Table创建临时视图
tableEnv.createTemporaryView("sensorView", sensorTable);
5 查看执行计划
-
Table API 提供了一种机制来解释计算表的逻辑和优化查询计划
-
查看执行计划,可以通过
TableEnvironment.explain(table)
方法或TableEnvironment.explain()
方法完成,返回一个字符串,描述三个计划- 优化的逻辑查询计划
- 优化后的逻辑查询计划
- 实际执行计划
String explaination = tableEnv.explain(resultTable); System.out.println(explaination);
6 流处理和关系代数的区别
Flink- 将表转换成DataStream | 查看执行计划 | 流处理和关系代数的区别 | 动态表 | 流式持续查询的过程 | 将流转换成动态表 | 持续查询 | 将动态表转换成 DS :https://blog.csdn.net/qq_40180229/article/details/106479537
Table API和SQL,本质上还是基于关系型表的操作方式;而关系型表、关系代数,以及SQL本身,一般是有界的,更适合批处理的场景。这就导致在进行流处理的过程中,理解会稍微复杂一些,需要引入一些特殊概念。
其实关系代数(主要就是指关系型数据库中的表)和SQL,主要就是针对批处理的,这和流处理有天生的隔阂。
关系代数(表)/SQL | 流处理 | |
---|---|---|
处理的数据对象 | 字段元组的有界集合 | 字段元组的无限序列 |
查询(Query)对数据的访问 | 可以访问到完整的数据输入 | 无法访问所有数据,必须持续"等待"流式输入 |
查询终止条件 | 生成固定大小的结果集后终止 | 永不停止,根据持续收到的数据不断更新查询结果 |
6.1 动态表(Dynamic Tables)
随着新数据的到来,不停地在之前的基础上更新结果。这样得到的表,在Flink Table API概念里,就叫做“动态表”(Dynamic Tables)。
- 动态表是 Flink 对流数据的 Table API 和 SQL 支持的核心概念
- 与表示批处理数据的静态表不同,动态表是随时间变化的
- 持续查询(Continuous Query)
- 动态表可以像静态的批处理表一样进行查询,查询一个动态表会产生持续查询(Continuous Query)
- 连续查询永远不会终止,并会生成另一个动态表
- 查询(Query)会不断更新其动态结果表,以反映其动态输入表上的更改。
6.2 动态表和持续查询
流式表查询的处理过程:
- 流被转换为动态表
- 对动态表计算连续查询,生成新的动态表
- 生成的动态表被转换回流
6.3 将流转换成动态表
- 为了处理带有关系查询的流,必须先将其转换为表
- 从概念上讲,流的每个数据记录,都被解释为对结果表的插入(Insert)修改操作本质上,我们其实是从一个、只有插入操作的changelog(更新日志)流,来构建一个表
来一条数据插入一条数据
6.4 持续查询
-
持续查询,会在动态表上做计算处理,并作为结果生成新的动态表。
与批处理查询不同,连续查询从不终止,并根据输入表上的更新更新其结果表。
在任何时间点,连续查询的结果在语义上,等同于在输入表的快照上,以批处理模式执行的同一查询的结果。
下图为一个点击事件流的持续查询,是一个分组聚合做count统计的查询。
6.4 将动态表转换成 DataStream
- 与常规的数据库表一样,动态表可以通过插入(Insert)、更新(Update)和删除(Delete)更改,进行持续的修改
- 将动态表转换为流或将其写入外部系统时,需要对这些更改进行编码
-
仅追加(Append-only)流
- 仅通过插入(Insert)更改来修改的动态表,可以直接转换为仅追加流
-
撤回(Retract)流
-
撤回流是包含两类消息的流:添加(Add)消息和撤回(Retract)消息
动态表通过将INSERT 编码为add消息、DELETE 编码为retract消息、UPDATE编码为被更改行(前一行)的retract消息和更新后行(新行)的add消息,转换为retract流。
-
-
Upsert(更新插入流)
-
Upsert流也包含两种类型的消息:Upsert消息和删除(Delete)消息
通过将INSERT和UPDATE更改编码为upsert消息,将DELETE更改编码为DELETE消息,就可以将具有唯一键(Unique Key)的动态表转换为流。
-
6.5 将动态表转换成DataStream
时间特性(Time Attributes)
1 概述
- 基于时间的操作(比如 Table API 和 SQL 中窗口操作),需要定义相关的时间语义和时间数据来源的信息
- Table 可以提供一个逻辑上的时间字段,用于在表处理程序中,指示时间和访问相应的时间戳
- 时间属性,可以是每个表schema的一部分。一旦定义了时间属性,它就可以作为一个字段引用,并且可以在基于时间的操作中使用
- 时间属性的行为类似于常规时间戳,可以访问,并且进行计算
2 定义处理时间(Processing Time)
- 处理时间语义下,允许表处理程序根据机器的本地时间生成结果。它是时间的最简单概念。它既不需要提取时间戳,也不需要生成 watermark
由DataStream转换成表时指定
-
在定义 Table Schema 期间,可以使用
.proctime
,指定字段名定义处理时间字段 -
这个proctime属性只能通过附加逻辑字段,来扩展物理schema。因此,只能在schema定义的末尾定义它
Table sensorTable = tableEnv.fromDataStream(dataStream,"id, temperature, pt.proctime");
定义Table Schema时指定
.withSchema(new Schema()
.field("id", DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.field("temperature",DataTypes.DOUBLE())
.field("pt",DataTypes.TIMESTAMP(3))
.proctime()
)
创建表的DDL中定义
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
3 定义事件事件(Event Time)
- 事件时间语义,允许表处理程序根据每个记录中包含的时间生成结果。这样即使在有乱序事件或者延迟事件时,也可以获得正确的结果。
- 为了处理无序事件,并区分流中的准时和迟到事件;Flink需要从事件数据中,提取时间戳,并用来推送事件时间的进展
- 定义事件事件,同样有三种方法:
- 由DataStream转换成表时指定
- 定义Table Schema时指定
- 在创建表的DDL中定义
由DataStream转换成表时指定
-
由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");
定义Table Schema时指定
.withSchema(new Schema()
.field("id", DataTypes.STRING())
.field("timestamp",DataTypes.BIGINT())
.rowtime(
new Rowtime()
.timestampsFromField("timestamp") // 从字段中提取时间戳
.watermarksPeriodicBounded(1000) // watermark延迟1秒
)
.field("temperature",DataTypes.DOUBLE())
)
创建表的DDL中定义
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
4 窗口
Flink-分组窗口 | Over Windows | SQL 中的 Group Windows | SQL 中的 Over Windows:https://blog.csdn.net/qq_40180229/article/details/106482095
- 时间语义,要配合窗口操作才能发挥作用。
- 在Table API和SQL中,主要有两种窗口
- Group Windows(分组窗口)
- 根据时间戳或行计数间隔,将行聚合到有限的组(Group)中,并对每个组的数据执行一次聚合函数
- Over Windows
- 针对每个输入行,计算相邻行范围内的聚合
- Group Windows(分组窗口)
4.1 Group Windows
-
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 windows)
- 滚动窗口(Tumbling windows)要用Tumble类来定义
// 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"))
- over:定义窗口长度
- on:用来分组(按时间间隔)或者排序(按行数)的时间字段
- as:别名,必须出现在后面的groupBy中
滑动窗口(Sliding windows)
- 滑动窗口(Sliding windows)要用Slide类来定义
// 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"))
- over:定义窗口长度
- every:定义滑动步长
- on:用来分组(按时间间隔)或者排序(按行数)的时间字段
- as:别名,必须出现在后面的groupBy中
会话窗口(Session windows)
- 会话窗口(Session windows)要用Session类来定义
// 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"))
- withGap:会话时间间隔
- on:用来分组(按时间间隔)或者排序(按行数)的时间字段
- as:别名,必须出现在后面的groupBy中
4.2 SQL中的Group Windows
Group Windows定义在SQL查询的Group By子句中
- TUMBLE(time_attr, interval)
- 定义一个滚动窗口,每一个参数是时间字段,第二个参数是窗口长度
- HOP(time_attr,interval,interval)
- 定义一个滑动窗口,第一个参数是时间字段,第二个参数是窗口滑动步长,第三个是窗口长度
- SESSION(time_attr,interval)
- 定义一个绘画窗口,第一个参数是时间字段,第二个参数是窗口间隔
测试代码
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)
4.3 Over Windows
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 Windows
- 可以在事件时间或处理时间,以及指定为时间间隔、或行计数的范围内,定义 Over windows
- 无界的 over window 是使用常量指定的
// 无界的事件时间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 Windows
- 有界的over window是用间隔的大小指定的
// 有界的事件时间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"))
4.4 SQL中的Over Windows
- 用 Over 做窗口聚合时,所有聚合必须在同一窗口上定义,也就是说必须是相同的分区、排序和范围
- 目前仅支持在当前行范围之前的窗口
- ORDER BY 必须在单一的时间属性上指定
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)
函数(Functions)
Flink-函数 | 用户自定义函数(UDF)标量函数 | 表函数 | 聚合函数 | 表聚合函数:https://blog.csdn.net/qq_40180229/article/details/106482550
1 用户自定义函数(UDF)
-
用户定义函数(User-defined Functions,UDF)是一个重要的特性,它们显著地扩展了查询的表达能力
一些系统内置函数无法解决的需求,我们可以用UDF来自定义实现
-
在大多数情况下,用户定义的函数必须先注册,然后才能在查询中使用
-
函数通过调用
registerFunction()
方法在 TableEnvironment 中注册。当用户定义的函数被注册时,它被插入到 TableEnvironment 的函数目录中,这样Table API 或 SQL 解析器就可以识别并正确地解释它
1.1 标量函数(Scalar Functions)
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
1.2 表函数(Table Fcuntions)
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
1.3 聚合函数(Aggregate Functions)
聚合,多对一,类似前面的窗口聚合
- 用户自定义聚合函数(User-Defined Aggregate Functions,UDAGGs)可以把一个表中的数据,聚合成一个标量值
- 用户定义的聚合函数,是通过继承 AggregateFunction 抽象类实现的
- AggregationFunction要求必须实现的方法
createAccumulator()
accumulate()
getValue()
- AggregateFunction 的工作原理如下:
- 首先,它需要一个累加器(Accumulator),用来保存聚合中间结果的数据结构;可以通过调用
createAccumulator()
方法创建空累加器 - 随后,对每个输入行调用函数的
accumulate()
方法来更新累加器 - 处理完所有行后,将调用函数的
getValue()
方法来计算并返回最终结果
- 首先,它需要一个累加器(Accumulator),用来保存聚合中间结果的数据结构;可以通过调用
测试代码
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)
1.4 表聚合函数
- 用户定义的表聚合函数(User-Defined Table Aggregate Functions,UDTAGGs),可以把一个表中数据,聚合为具有多行和多列的结果表
- 用户定义表聚合函数,是通过继承 TableAggregateFunction 抽象类来实现的
- AggregationFunction 要求必须实现的方法:
createAccumulator()
accumulate()
emitValue()
- TableAggregateFunction 的工作原理如下:
- 首先,它同样需要一个累加器(Accumulator),它是保存聚合中间结果的数据结构。通过调用
createAccumulator()
方法可以创建空累加器。 - 随后,对每个输入行调用函数的
accumulate()
方法来更新累加器。 - 处理完所有行后,将调用函数的
emitValue()
方法来计算并返回最终结果。
- 首先,它同样需要一个累加器(Accumulator),它是保存聚合中间结果的数据结构。通过调用
[测试代码]
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