SQL 上下⽂:TableEnvironment
TableEnvironment 包含的功能如下:
Catalog 管理、表管理、SQL 查询、UDF 管理、UDF 扩展、DataStream\Table\SQL API 转换。
1.通过 EnvironmentSettings 创建 TableEnvironment
// 1. 设置环境信息
EnvironmentSettings settings = EnvironmentSettings
.newInstance()
.inStreamingMode() // 声明为流任务
//.inBatchMode() // 声明为批任务
.build();
// 2. 创建 TableEnvironment
TableEnvironment tEnv = TableEnvironment.create(settings);
EnvironmentSettings 的其它配置项:
2.通过已有的 StreamExecutionEnvironment 创建 TableEnvironment
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment streamTableEnvironment = StreamTableEnvironment.create(env);
在 1.13 版本中,如果是 inStreamingMode ,创建的 TableEnvironment 实例为 StreamTableEnvironmentImpl 。
如果是 inBatchMode ,创建的 TableEnvironment 实例为 TableEnvironmentImpl 。
2)SQL 中表的概念
⼀个表的全名(标识)由三部分组成: Catalog 名称.数据库名称.表名称,如果 Catalog 名称 或者 数据库 名称 没有指明,会使⽤默认值 default。
表可以是常规的(外部表 TABLE),也可以是虚拟的(视图 VIEW)。
外部表 TABLE:描述的是外部数据,例如⽂件(HDFS)、消息队列(Kafka)等。
视图 VIEW:从已经存在的表中创建,视图⼀般是⼀个 SQL 逻辑的查询结果。
3)SQL 临时表、永久表
表(视图、外部表)可以是临时的,并与单个 Flink session(可以理解为 Flink 任务运⾏⼀次就是⼀个 session) 的⽣命周期绑定。
表(视图、外部表)也可以是永久的,并且对多个 Flink session 都⽣效。
临时表:通常保存于内存中并且仅在创建它们的 Flink session(可以理解为⼀次 Flink 任务的运⾏)持续期间存在。这些表对于其它 session(即其他 Flink 任务或⾮此次运⾏的 Flink 任务)是不可⻅的。因为这个表的元数据没有被持久化。
-- 临时外部表
CREATE TEMPORARY TABLE source_table (
user_id BIGINT,
`name` STRING
) WITH (
'connector' = 'user_defined',
'format' = 'json',
'class.name' = 'flink.examples.sql._03.source_sink.table.user_defined.UserDefinedS
);
-- 临时视图
CREATE TEMPORARY VIEW query_view as
SELECT
*
FROM source_table;
永久表:需要外部 Catalog(例如 Hive Metastore)来持久化表的元数据。⼀旦永久表被创建,它将对任何连接到这个 Catalog 的 Flink session 可⻅且持续存在,直⾄从 Catalog 中被明确删除。
-- 永久外部表。需要外部 Catalog 持久化!!!
CREATE TABLE source_table (
user_id BIGINT,
`name` STRING
) WITH (
'connector' = 'user_defined',
'format' = 'json',
'class.name' = 'flink.examples.sql._03.source_sink.table.user_defined.UserDefinedS
);
-- 永久视图。需要外部 Catalog 持久化!!!
CREATE VIEW query_view as
SELECT
*
FROM source_table;
如果临时表和永久表使⽤了相同的名称(Catalog名.数据库名.表名),那么在这个 Flink session 中,任务访问到这个表时,访问到的永远是临时表(即相同名称的表,临时表会屏蔽永久表)。
4)SQL 外部数据表
⽬前在实时数据的场景中多以消息队列作为数据表,此处以 Kafka 为例创建⼀个外部数据表。
Table API 创建外部表:
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
EnvironmentSettings settings = EnvironmentSettings
.newInstance()
.useBlinkPlanner()
.inStreamingMode()
.build();
StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings);
// kafka 数据源
DataStream<Row> r = env.addSource(new FlinkKafkaConsumer<Row>(...));
// 将 DataStream 转为⼀个 Table API 中的 Table 对象进⾏使⽤
Table sourceTable = tEnv.fromDataStream(r
, Schema
.newBuilder()
.column("f0", "string")
.column("f1", "string")
.column("f2", "bigint")
.columnByExpression("proctime", "PROCTIME()")
.build());
tEnv.createTemporaryView("source_table", sourceTable);
String selectWhereSql = "select f0 from source_table where f1 = 'b'";
Table resultTable = tEnv.sqlQuery(selectWhereSql);
tEnv.toRetractStream(resultTable, Row.class).print();
env.execute();
}
上述案例中,Table API 将⼀个 DataStream 的结果集通过 StreamTableEnvironment::fromDataStream 转为⼀个 Table 对象来使⽤。
SQL API 创建外部数据表:
EnvironmentSettings settings = EnvironmentSettings
.newInstance()
.useBlinkPlanner()
.inStreamingMode()
.build();
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings);
// SQL API 执⾏ create table 创建表
tEnv.executeSql(
"CREATE TABLE KafkaSourceTable (\n"
+ " `f0` STRING,\n"
+ " `f1` STRING\n"
+ ") WITH (\n"
+ " 'connector' = 'kafka',\n"
+ " 'topic' = 'topic',\n"
+ " 'properties.bootstrap.servers' = 'localhost:9092',\n"
+ " 'properties.group.id' = 'testGroup',\n"
+ " 'format' = 'json'\n"
+ ")"
);
Table t = tEnv.sqlQuery("SELECT * FROM KafkaSourceTable");
创建⽅式:使⽤ Create Table xxx DDL 定义⼀个 Kafka 数据源(输⼊)表(也可以是 Kafka 数据 汇(输出)表)。
5)SQL 视图 VIEW
VIEW 就是⼀段 SQL 逻辑的查询结果,视图 VIEW 在 Table API 中的体现就是:⼀个 Table 的 Java 对象,封装了⼀段查询逻辑。
Table API 创建 VIEW
EnvironmentSettings settings = EnvironmentSettings
.newInstance()
.inStreamingMode() // 声明为流任务
.build();
TableEnvironment tEnv = TableEnvironment.create(settings);
// Table API 中的⼀个 Table 对象
Table projTable = tEnv.from("X").select(...);
// 将 projTable 创建为⼀个叫做 projectedTable 的 VIEW
tEnv.createTemporaryView("projectedTable", projTable);
Table API 使⽤了 TableEnvironment::createTemporaryView 接⼝将⼀个 Table 对象创建为⼀个 VIEW。
SQL API 创建 VIEW:
EnvironmentSettings settings = EnvironmentSettings
.newInstance()
.inStreamingMode() // 声明为流任务
.build();
TableEnvironment tEnv = TableEnvironment.create(settings);
String sql = "CREATE TABLE source_table (\n"
+ " user_id BIGINT,\n"
+ " `name` STRING\n"
+ ") WITH (\n"
+ " 'connector' = 'user_defined',\n"
+ " 'format' = 'json',\n"
+ " 'class.name' = 'flink.examples.sql._03.source_sink.table.user_defined.User',\n"
+ ");\n"
+ "\n"
+ "CREATE TABLE sink_table (\n"
+ " user_id BIGINT,\n"
+ " name STRING\n"
+ ") WITH (\n"
+ " 'connector' = 'print'\n"
+ ");\n"
+ "CREATE VIEW query_view as\n" // 创建 VIEW
+ "SELECT\n"
+ " *\n"
+ "FROM source_table\n"
+ ";\n"
+ "INSERT INTO sink_table\n"
+ "SELECT\n"
+ " *\n"
+ "FROM query_view;";
Arrays.stream(sql.split(";"))
.forEach(tEnv::executeSql);
SQL API 通过⼀段 CREATE VIEW query_view as select * from source_table 来创建的 VIEW。
在 Table API 中的⼀个 Table 对象被后续的多个查询使⽤的场景下:
Table 对象不会真的产⽣⼀个中间表供下游多个查询去引⽤,即多个查询不共享这个 Table 的结果,可以理解为是⼀种中间表的简化写法,不会先产出⼀个中间表结果,然后将这个结果在下游多个查询中复⽤,后续的多个查询会将这个 Table 的逻辑执⾏多次,类似于 with tmp as (DML) 的语法。
6)⼀个 SQL 查询案例
案例场景:计算每⼀种商品(sku_id 唯⼀标识)的售出个数、总销售额、平均销售额、最低价、最⾼价
数据准备:数据源为商品的销售流⽔(sku_id:商品,price:销售价格),然后写⼊到 Kafka 的指定 topic(sku_id:商品,count_result:售出个数、sum_result:总销售额、avg_result:平均销售额、 min_result:最低价、max_result:最⾼价)
EnvironmentSettings settings = EnvironmentSettings
.newInstance()
.inStreamingMode() // 声明为流任务
//.inBatchMode() // 声明为批任务
.build();
TableEnvironment tEnv = TableEnvironment.create(settings);
// 1. 创建⼀个数据源(输⼊)表,这⾥的数据源是 flink ⾃带的⼀个随机 mock 数据的数据源。
String sourceSql = "CREATE TABLE source_table (\n"
+ " sku_id STRING,\n"
+ " price BIGINT\n"
+ ") WITH (\n"
+ " 'connector' = 'datagen',\n"
+ " 'rows-per-second' = '1',\n"
+ " 'fields.sku_id.length' = '1',\n"
+ " 'fields.price.min' = '1',\n"
+ " 'fields.price.max' = '1000000'\n"
+ ")";
// 2. 创建⼀个数据汇(输出)表,输出到 kafka 中
String sinkSql = "CREATE TABLE sink_table (\n"
+ " sku_id STRING,\n"
+ " count_result BIGINT,\n"
+ " sum_result BIGINT,\n"
+ " avg_result DOUBLE,\n"
+ " min_result BIGINT,\n"
+ " max_result BIGINT,\n"
+ " PRIMARY KEY (`sku_id`) NOT ENFORCED\n"
+ ") WITH (\n"
+ " 'connector' = 'upsert-kafka',\n"
+ " 'topic' = 'test',\n"
+ " 'properties.bootstrap.servers' = 'localhost:9092',\n"
+ " 'key.format' = 'json',\n"
+ " 'value.format' = 'json'\n"
+ ")";
// 3. 执⾏⼀段 group by 的聚合 SQL 查询
String selectWhereSql = "insert into sink_table\n"
+ "select sku_id,\n"
+ " count(*) as count_result,\n"
+ " sum(price) as sum_result,\n"
+ " avg(price) as avg_result,\n"
+ " min(price) as min_result,\n"
+ " max(price) as max_result\n"
+ "from source_table\n"
+ "group by sku_id";
tEnv.executeSql(sourceSql);
tEnv.executeSql(sinkSql);
tEnv.executeSql(selectWhereSql);
7)SQL 与 DataStream API 的转换
1.13 版本,只有流任务⽀持 Table 和 DataStream 之间的转换。
1.14 版本,流和批任务均⽀持 Table 和 DataStream 之间的转换。
StreamTableEnvironment::toDataStream
StreamTableEnvironment::fromDataStream