Flin TableAPI & SQL (一)

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("表名")

posted @ 2022-11-17 17:27  MrSponge  Views(83)  Comments(0Edit  收藏  举报