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
相关推荐
青云交7 小时前
大数据新视界 -- Impala 性能优化:量子计算启发下的数据加密与性能平衡(下)(30 / 30)
大数据·hive·量子计算·数据加密·impala·量子密钥分发·性能平衡
Elastic 中国社区官方博客8 小时前
使用 Elastic 收集 Windows 遥测数据:ETW Filebeat 输入简介
大数据·windows·elasticsearch·搜索引擎·全文检索·可用性测试
鸡c11 小时前
IM项目-----ElasticSearch
大数据·elasticsearch·搜索引擎
Java 第一深情11 小时前
Flink数据源的读写介入体系
大数据·flink
雷神乐乐11 小时前
MyBatis中的${}和#{}区别
数据库·sql·mybatis·javaweb
天冬忘忧12 小时前
Kafka 消费者全面解析:原理、消费者 API 与Offset 位移
大数据·kafka
jlting19512 小时前
《智慧教育实时数据分析推荐项目》详细分析
大数据·redis·sql·kafka·database
青云交12 小时前
大数据新视界 -- Hive 数据仓库:架构深度剖析与核心组件详解(上)(1 / 30)
大数据
EasyNVR12 小时前
NVR管理平台EasyNVR多品牌NVR管理工具的流媒体视频融合与汇聚管理方案
大数据·网络·安全·音视频·监控·视频监控
java1234_小锋13 小时前
在Elasticsearch中,是怎么根据一个词找到对应的倒排索引的?
大数据·elasticsearch·搜索引擎