FlinkSQL 中上下文、表、视图解析

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
相关推荐
北漂老男孩几秒前
Hadoop HDFS 体系结构与文件读写流程剖析
大数据·hadoop·hdfs·学习方法
小李是个程序1 小时前
数据库完整性
数据库·sql
TDengine (老段)1 小时前
TDengine 高级功能——流计算
大数据·物联网·flink·linq·时序数据库·tdengine·涛思数据
TDengine (老段)2 小时前
TDengine 高级功能——读缓存
大数据·数据库·缓存·时序数据库·tdengine·涛思数据·iotdb
雷神乐乐2 小时前
Oracle中的循环——FOR循环、WHILE循环和LOOP循环
数据库·sql·oracle·循环
TDengine (老段)2 小时前
TDengine 运维——巡检工具(安装前预配置)
大数据·运维·数据库·时序数据库·iot·tdengine·涛思数据
Mikhail_G3 小时前
Python应用continue关键字初解
大数据·运维·开发语言·python·数据分析
audyxiao0015 小时前
数据挖掘顶刊《IEEE Transactions on Knowledge and Data Engineering》2025年5月研究热点都有些什么?
大数据·人工智能·数据挖掘·大模型·图技术·智能体·时序动态建模
切糕师学AI6 小时前
SQL 筛选出在表1但不在表2中的数据
数据库·sql
亚马逊云开发者6 小时前
使用 Amazon Q Developer CLI 快速搭建各种场景的 Flink 数据同步管道
sql