Flink TableAPI和SQL的基本运用介绍
在Flink中,TableAPI 和 SQL 可以看作是一体的,TableAPI可以将环境中的数据转换成对应的一张表,或者将表里的转换输出到外部系统,然后可以执行SQL语句来进行一个查询和统计。
1、 快速上手
添加相关依赖:
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-api-java-bridge_2.12</artifactId>
<version>1.13.0</version>
</dependency>
<!-- 添加本地IDE中运行TableAPI & SQL 的相关依赖 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-planner-blink_2.12</artifactId>
<version>1.13.0</version>
</dependency>
<!-- 添加自定义的格式(比如和kafka交互)或者用户自定义函数依赖 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-common</artifactId>
<version>1.13.0</version>
</dependency>
SimpleTableExample.java:
package com.peng;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import static org.apache.flink.table.api.Expressions.$;
/**
* @author 海绵先生
* @Description TODO 简单演示TableAPI的流程
* @date 2022/11/13-15:03
*/
public class SimpleTableExample {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
// 创建表执行环境
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
// source
DataStreamSource<Event> eventDataStreamSource = env.addSource(new ClickSource());
// 将DataStream转换成Table
// 由于Even和ClickSource类不在同一个类下,所以涉及到DataStream转换成Table时,系统可能会自动把字段命名为f0...等
// 所以要用as进行一个重命名
Table eventTable = tableEnv.fromDataStream(eventDataStreamSource).as("user","url", "timestamp");
// 注:用as方法进行重命名时,Event必须是一个public类
// 直接写SQL进行转换
// timestamp为SQL里的关键字,加个反引号就相当于不把它当关键字
Table resultTable = tableEnv.sqlQuery("select user, url, `timestamp` from " + eventTable);// from后面记得加空格
//TableResult tableResult = tableEnv.executeSql("select 'user', 'url', '`timestamp`' from " + eventTable);
// 注:不知道为什么,执行sqlQuery语句时查找不到对应字段 执行 * 是可以运行的(已解决,看上面)
// 基于Table直接转换
Table resultTable2 = eventTable.select($("user"), $("url"))
.where($("user").isEqual("zhangsan"));
//resultTable.print() ERROR 一张表是打印不了的
tableEnv.toDataStream(resultTable).print("resultTable:");
//eventTable.printSchema();
//tableResult.print();
tableEnv.toDataStream(resultTable2).print("resultTable2:");
env.execute();
}
}
Event.java:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Event{
private String user;
private String url;
private long timestamp;
@Override
public String toString() {
return "Event{" +
"user='" + user + '\'' +
", url='" + url + '\'' +
", timestamp=" + new Timestamp(timestamp) +
'}';
}
}
ClickSource.java:
public class ClickSource extends RichParallelSourceFunction<Event> {
private String[] names = {"zhangsan","lisi","wangwu","zhaoliu"};
private String[] urls = {"./user","./lookspouce","./like"};
private boolean flag = true;
@Override
public void run(SourceContext<Event> ctx) throws Exception {
Random random = new Random();
while (flag){
String user = names[random.nextInt(4)];
String url = urls[random.nextInt(3)];
long timeMillis = System.currentTimeMillis();
ctx.collect(new Event(user,url,timeMillis));
Thread.sleep(1000);// 每秒产生一次数据
}
}
@Override
public void cancel() {
flag = false;
}
}
可以看出:TableAPI
的流程和 DataStream
的流程很像 env -> source -> transmation -> sink -> execute
另外要值得注意的是,DataStream
数据 转换成 Table
类型 时,系统可能会自动将字段重命名为f0....fn,出现找不到字段的情况。
2、 基本API
2.1 程序架构
// 创建表环境
TableEnvironment tableEnv = ...;
// 创建临时输入表,连接外部系统读取数据
tableEnv.executeSql("CREATE TEMPORARY TABLE inputTable ... WITH ( 'connector'
= ... )");
// 注册一个临时表,连接到外部系统,用于输出
tableEnv.executeSql("CREATE TEMPORARY TABLE outputTable ... WITH ( 'connector'
= ... )");
// 执行 SQL 对表进行查询转换,得到一个新的表
Table table1 = tableEnv.sqlQuery("SELECT ... FROM inputTable... ");
// 使用 Table API 对表进行查询转换,得到一个新的表
Table table2 = tableEnv.from("inputTable").select(...);
// 将得到的结果写入输出表
TableResult tableResult = table1.executeInsert("outputTable");
2.2 创建表环境
对于 Flink 这样的流处理框架来说,数据流和表在结构上还是有所区别的。所以使用 Table API 和 SQL 需要一个特别的运行时环境,这就是所谓的“表环境”(TableEnvironment)。它主要负责:
-
(1) 注册Catalog 和表;
-
(2) 执行 SQL 查询;
-
(3) 注册用户自定义函数(UDF);
-
(4) DataStream 和表之间的转换。
这里的 Catalog 就是“目录”,与标准 SQL 中的概念是一致的,主要用来管理所有数据库(database)和表(table)的元数据(metadata)。通过 Catalog 可以方便地对数据库和表进行查询的管理,所以可以认为我们所定义的表都会“挂靠”在某个目录下,这样就可以快速检索。在表环境中可以由用户自定义Catalog,并在其中注册表和自定义函数(UDF)。默认的 Catalog就叫作 default_catalog。
每个表和 SQL 的执行,都必须绑定在一个表环境(TableEnvironment)中。TableEnvironment是 Table API 中提供的基本接口类,可以通过调用静态的 create()
方法来创建一个表环境实例。方法需要传入一个环境的配置参数 EnvironmentSettings
,它可以指定当前表环境的执行模式和计划器(planner)。执行模式有批处理和流处理两种选择,默认是流处理模式;计划器默认使用 blink planner。
package com.peng;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;
import org.apache.flink.table.api.bridge.java.BatchTableEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
/**
* @author 海绵先生
* @Description TODO 5种创建表环境的方法
* @date 2022/11/16-15:54
*/
public class CommonApiTest {
public static void main(String[] args) {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
// TODO 方法一 通过静态方法create()创建,直接传入流处理环境参数
//StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
// TODO 方法二 自定义环境配置来创建表执行环境
EnvironmentSettings settings = EnvironmentSettings.newInstance()
.inStreamingMode()
.useBlinkPlanner()
.build();
// 这样就不需要传 env
TableEnvironment tableEnv = TableEnvironment.create(settings);
// TODO 方法三 基于老版本planner进行流处理
EnvironmentSettings settings2 = EnvironmentSettings.newInstance()
.inStreamingMode()
.useOldPlanner()
.build();
TableEnvironment tableEnv2 = TableEnvironment.create(settings2);
// TODO 方法四 基于老版本 planner 进行批处理
ExecutionEnvironment batchEnv = ExecutionEnvironment.getExecutionEnvironment();
BatchTableEnvironment batchTableEnvironment = BatchTableEnvironment.create(batchEnv);
// TODO 方法五 基于blink 版本planner进行批处理
EnvironmentSettings settings3 = EnvironmentSettings.newInstance()
.inBatchMode()
.useBlinkPlanner()
.build();
TableEnvironment tableEnv3 = TableEnvironment.create(settings3);
}
}
2.3 创建表
在代码中,我们可以调用表环境的 executeSql()方法,可以传入一个 DDL 作为参数执行SQL 操作。这里我们传入一个CREATE 语句进行表的创建,并通过 WITH 关键字指定连接到外部系统的连接器
tableEnv.executeSql("CREATE [TEMPORARY] TABLE MyTable ... WITH ( 'connector'
= ... )");
连接器表
package com.peng;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;
import org.apache.flink.table.api.bridge.java.BatchTableEnvironment;
/**
* @author 海绵先生
* @Description TODO 五种创建表环境的方法
* @date 2022/11/16-15:54
*/
public class CommonApiTest {
public static void main(String[] args) {
// 1. 创建表环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
// TODO 方法一 直接传入流处理环境参数
//StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
// TODO 方法二 自定义环境配置来创建表执行环境
EnvironmentSettings settings = EnvironmentSettings.newInstance()
.inStreamingMode()
.useBlinkPlanner()
.build();
// 这样就不需要传 env
TableEnvironment tableEnv = TableEnvironment.create(settings);
// 2. 创建表
String creatDDL = "create table clickTable(" +
"user STRING," +
"url STRING," +
")" +
"WITH(" +
" 'connector' = 'filesystem'," +
" 'path' = 'input/clicks.txt'," +
" 'format' = 'csv'" + // csv文件是以 ',' 进行分割的
")";
tableEnv.executeSql(creatDDL);
// 创建一张用于输出的表
String createOutDDL = "CREATE TABLE outTable (" +
" url STRING, " +
" user STRING, " +
")" +
" WITH(" +
" 'connector' = 'filesystem', " +
" 'path' = 'output'," +
" 'format' = 'csv'" + // csv文件是以 ',' 进行分割的
")";
}
}
此处创建的表跟 快速上手
里的不一样,通过executeSql执行的SQL语句创建的表时可以直接被引用的,是当前真实存在的一张表。
更正
本人用上面的方法创建表时,是输不出东西的,找不出原因 QAQ
更改为下面的格式
tableEnv
.connect(new FileSystem().path(inputPath)) //连接文件
// .withFormat(new OldCsv()) //旧版用这个,以csv格式对数据格式化
.withFormat(new Csv()) //新版本csv,需在pom.xml引入依赖后使用,相关依赖见表查询
.withSchema( new Schema()// 定义表的格式
.field("id", DataTypes.STRING())
.field("timestamp", DataTypes.BIGINT())
.field("temperature", DataTypes.DOUBLE())
) //定义表结构
.createTemporaryTable(“inputTable”) // 创建临时表 注册表
通过tableEnv.from()获取数据
tableEnv.from("inputTable");
通过 connector 声明创建表
package com.peng;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.*;
import org.apache.flink.table.api.bridge.java.BatchTableEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import org.apache.flink.table.descriptors.Csv;
import org.apache.flink.table.descriptors.FileSystem;
import org.apache.flink.table.descriptors.Schema;
import static org.apache.flink.table.api.Expressions.$;
/**
* @author 海绵先生
* @Description TODO 五种创建表环境->创建表->执行SQL
* @date 2022/11/16-15:54
*/
public class CommonApiTest {
public static void main(String[] args) {
// TODO 方法二 自定义环境配置来创建表执行环境
// 基于blink planner的流处理
EnvironmentSettings settings = EnvironmentSettings.newInstance()
.inStreamingMode()
.useBlinkPlanner()
.build();
// 这样就不需要传 env
TableEnvironment tableEnv = TableEnvironment.create(settings);
// 2. 创建表
String creatDDL = "create table clickTable(" +
" user_name STRING," +
" url STRING " +
" ) " +
" WITH( " +
" 'connector' = 'filesystem'," +
" 'path' = 'input/clicks.txt'," +
" 'format' = 'csv'" + // csv文件是以 ',' 进行分割的
")";
tableEnv.connect(new FileSystem().path("input/clicks.txt"))
.withFormat(new Csv())
.withSchema(new Schema()
.field("user_name", DataTypes.STRING())
.field("url", DataTypes.STRING())
.field("ts", DataTypes.BIGINT()))
.createTemporaryTable("clickTable");
// 调用TableAPI 进行表的查询
Table clickTable = tableEnv.from("clickTable");
Table resultTable = clickTable.where($("user_name").isEqual("zhangsan"))
.select($("user_name"), $("url"));
tableEnv.createTemporaryView(" result2 ", resultTable);
// 执行SQL进行表的查询转换 result是关键字,如果使用要用反引号括起来
Table resultTable2 = tableEnv.sqlQuery("select user_name,url from result2 ");
// 创建一张用于输出的表
String createOutDDL = "CREATE TABLE outTable (" +
" user_name STRING, " +
" url STRING " +
" )" +
" WITH(" +
" 'connector' = 'filesystem'," +
" 'path' = 'output'," +
" 'format' = 'csv'" + // csv文件是以 ',' 进行分割的
")";
//tableEnv.executeSql(createOutDDL);
tableEnv.connect(new FileSystem().path("output"))
.withFormat(new Csv())
.withSchema(new Schema()
.field("user_name", DataTypes.STRING())
.field("url", DataTypes.STRING()))
.createTemporaryTable("outTable");
}
}
Flink的Table API 中提供的一个向外部系统写入数据的通用接口,可以支持不同的文件格式(比如 CSV、Parquet)、存储数据库(比如 JDBC、HBase、Elasticsearch)和消息队列(比如 Kafka),只要在.connect()方法中定义就行了。
虚拟表
在环境中用SQL查询语句创建的一张虚拟表,类似SQL里的view视图,方法跟 快速上手
里的一样
Table newTable = tableEnv.sqlQuery("SELECT ... FROM MyTable... ");
通过调用表环境的sqlQuery()
方法,直接传入一条 SQL语句
作为参数,得到一个Table对象。
tableEnv.createTemporaryView("NewTable", newTable);
再通过调用表环境的 createTemporaryView()
方法,传入要 命名的表名 和 Table对象,将其SQL语句执行的内容作为虚拟表NewTable
的数据。
虚拟表跟连接器表不同,虚拟表是不存在的一张表,只有再调用该表名时,才会将该表名的SQL语句嵌套进去,作为表的数据。
2.4 表的查询
基于表执行SQL 语句,是我们最为熟悉的查询方式。Flink 基于 Apache Calcite 来提供对SQL 的支持,Calcite 是一个为不同的计算平台提供标准 SQL 查询的底层工具,很多大数据框架比如 Apache Hive、Apache Kylin 中的SQL 支持都是通过集成 Calcite 来实现的。
在代码中,我们只要调用表环境的 sqlQuery()
方法,传入一个字符串形式的 SQL 查询语句就可以了。执行得到的结果,是一个 Table 对象。
表的查询有两种
方法
//第一步: 先从表环境中取表 => tableEnv.from("要取的表名")
Table clickTable = tableEnv.from("clickTable");
// TODO 方法一:
// 第二步:通过上一步的Table返回值,直接.where()...执行相关查询语句
Table resultTable = clickTable.where($("user_name").isEqual("zhangsan"))
.select($("user_name"), $("url"));
// TODO 方法二:通过表环境创建临时视图
tableEnv.createTemporaryView(" result2 ", resultTable);// 参数一:"result2"为表的别名,参数二:resultTable为表环境中的表名。
// 执行SQL进行表的查询转换 如果使用关键字,要用反引号括起来
Table resultTable2 = tableEnv.sqlQuery("select user_name,url from result2 ");
实践:
获取不了数据啊QAQ,求解
package com.peng;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.TableEnvironment;
import org.apache.flink.table.api.TableResult;
import org.apache.flink.table.api.bridge.java.BatchTableEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import static org.apache.flink.table.api.Expressions.$;
/**
* @author 海绵先生
* @Description TODO
* @date 2022/11/16-15:54
*/
public class CommonApiTest {
public static void main(String[] args) {
// TODO 方法二 自定义环境配置来创建表执行环境
// 基于blink planner的流处理
EnvironmentSettings settings = EnvironmentSettings.newInstance()
.inStreamingMode()
.useBlinkPlanner()
.build();
// 这样就不需要传 env
TableEnvironment tableEnv = TableEnvironment.create(settings);
// 2. 创建表
String creatDDL = "create table clickTable(" +
" user_name STRING," +
" url STRING " +
" ) " +
" WITH( " +
" 'connector' = 'filesystem'," +
" 'path' = 'input/clicks.txt'," +
" 'format' = 'csv'" + // csv文件是以 ',' 进行分割的
")";
tableEnv.executeSql(creatDDL);
// 调用TableAPI 进行表的查询
Table clickTable = tableEnv.from("clickTable");
Table resultTable = clickTable.where($("user_name").isEqual("zhangsan"))
.select($("user_name"), $("url"));
tableEnv.createTemporaryView(" result2 ", resultTable);
// 执行SQL进行表的查询转换 result是关键字,如果使用要用反引号括起来
Table resultTable2 = tableEnv.sqlQuery("select user_name,url from result2 ");
// 创建一张用于输出的表
String createOutDDL = "CREATE TABLE outTable (" +
" user_name STRING, " +
" url STRING " +
" )" +
" WITH(" +
" 'connector' = 'filesystem'," +
" 'path' = 'output'," +
" 'format' = 'csv'" + // csv文件是以 ',' 进行分割的
")";
tableEnv.executeSql(createOutDDL);
// 输出表
resultTable.executeInsert("outTable");// outTable对应上门创建的表名
}
}
添加依赖:csv文件格式依赖
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-csv</artifactId>
<!-- <version>${flink.version}</version> -->
<version>1.13.0</version>
</dependency>
更新
package com.peng;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.*;
import org.apache.flink.table.api.bridge.java.BatchTableEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import org.apache.flink.table.descriptors.Csv;
import org.apache.flink.table.descriptors.FileSystem;
import org.apache.flink.table.descriptors.Schema;
import static org.apache.flink.table.api.Expressions.$;
/**
* @author 海绵先生
* @Description TODO 五种创建表环境->创建表->执行SQL
* @date 2022/11/16-15:54
*/
public class CommonApiTest {
public static void main(String[] args) {
// TODO 方法二 自定义环境配置来创建表执行环境
// 基于blink planner的流处理
EnvironmentSettings settings = EnvironmentSettings.newInstance()
.inStreamingMode()
.useBlinkPlanner()
.build();
// 这样就不需要传 env
TableEnvironment tableEnv = TableEnvironment.create(settings);
// 2. 创建表
tableEnv.connect(new FileSystem().path("input/clicks.txt"))
.withFormat(new Csv())
.withSchema(new Schema()
.field("user_name", DataTypes.STRING())
.field("url", DataTypes.STRING())
.field("ts", DataTypes.BIGINT()))
.createTemporaryTable("clickTable");
// 调用TableAPI 进行表的查询
// 从表环境中取表
Table clickTable = tableEnv.from("clickTable");
Table resultTable = clickTable.where($("user_name").isEqual("zhangsan"))
.select($("user_name"), $("url"));
tableEnv.createTemporaryView(" result2 ", resultTable);
// 执行SQL进行表的查询转换 result是关键字,如果使用要用反引号括起来
Table resultTable2 = tableEnv.sqlQuery("select user_name,url from result2 ");
// 创建一张用于输出的表
tableEnv.connect(new FileSystem().path("output"))
.withFormat(new Csv())
.withSchema(new Schema()
.field("user_name", DataTypes.STRING())
.field("url", DataTypes.STRING()))
.createTemporaryTable("outTable");
// 输出表
resultTable.executeInsert("outTable");// outTable对应上门创建的表名
}
}
运行结果:
2.5 表的输出
其实在上面就用的了表的输出,将数据输出到外部系统
// 注册表,用于输出数据到外部系统
tableEnv
.connect() // 连接的文件
.withFormat() // 确定数据的分隔方式
.withSchema() //定义表结构
.createTemporaryTable("OutputTable") // 创建临时表 注册表
// 经过查询转换,得到结果表
Table result = ...
// 将结果表写入已注册的输出表中
result.executeInsert("OutputTable");
Flink的Table API 中提供的一个向外部系统写入数据的通用接口,可以支持不同的文件格式(比如 CSV、Parquet)、存储数据库(比如 JDBC、HBase、Elasticsearch)和消息队列(比如 Kafka),只要在.connect()方法中定义就行了。
表的输入跟输出类似,调用的是表环境中的from方法 tableEnv.from("表名")