Flink TableAPI和SQL(五)基本API(四)流和表之间的转换

文章目录

  • 将表(Table)转换成流(DataStream)
  • 将流(DataStream)转换成表(Table)
  • 支持的数据类型
  • 全代码展示:

在 Flink 中我们可以将 Table 再转换成 DataStream,然后进行打印输出。这就涉及了表和流的转换。

将表(Table)转换成流(DataStream)

(1)调用 toDataStream()方法
将一个 Table 对象转换成 DataStream 非常简单,只要直接调用表环境的方法 toDataStream()就可以了。

tableEnv.toDataStream(result);

这里需要将要转换的 Table 对象作为参数传入。

(2)调用 toChangelogStream()方法

tableEnv.toChangelogStream(aggResult);

对于这样有更新操作的表,我们不要试图直接把它转换成 DataStream 打印输出,而是记录一下它的“更新日志”(change log)。这样一来,对于表的所有更新操作,就变成了一条更新日志的流,我们就可以转换成流打印输出了。

与“更新日志流”(Changelog Streams)对应的,是那些只做了简单转换、没有进行聚合统计的表,它们的特点是数据只会插入、不会更新,所以也被叫作“仅插入流”(Insert-Only Streams)。

总结:

1)toDataStream()方法 叫做 “仅插入流”(Insert-Only Streams)。
2)toChangelogStream()方法 叫做 “更新日志流”(Changelog Streams)。

将流(DataStream)转换成表(Table)

(1)调用 fromDataStream()方法
想要将一个 DataStream 转换成表也很简单,可以通过调用表环境的fromDataStream()方法来实现,返回的就是一个 Table 对象。

Table eventTable = tableEnv.fromDataStream(dataStream);

另外,我们还可以在 fromDataStream()方法中增加参数,用来指定提取哪些属性作为表中的字段名,并可以任意指定位置:

// 提取 Event 中的 timestamp 和 url 作为表中的列
Table eventTable2 = tableEnv.fromDataStream(dataStream, $("timestamp"), $("url"));

需要注意的是,timestamp 本身是 SQL 中的关键字,所以我们在定义表名、列名时要尽量避免。这时可以通过表达式的 as()方法对字段进行重命名:

// 将 timestamp 字段重命名为 ts
Table eventTable2 = tableEnv.fromDataStream(dataStream, $("timestamp").as("ts"), $("url"));

(2)调用 createTemporaryView()方法
调用 fromDataStream()方法简单直观,可以直接实现 DataStream 到 Table 的转换;不过如果我们希望直接在 SQL 中引用这张表,就还需要调用表环境的 createTemporaryView()方法来创建虚拟视图了。

对于这种场景,也有一种更简洁的调用方式。我们可以直接调用createTemporaryView()方法创建虚拟表,传入的两个参数,第一个依然是注册的表名,而第二个可以直接就是DataStream。之后仍旧可以传入多个参数,用来指定表中的字段

tableEnv.createTemporaryView("EventTable", dataStream, $("timestamp").as("ts"),$("url"));

(3)调用 fromChangelogStream ()方法
表环境还提供了一个方法 fromChangelogStream(),可以将一个更新日志流转换成表。这个方法要求流中的数据类型只能是 Row,而且每一个数据都需要指定当前行的更新类型(RowKind);所以一般是由连接器帮我们实现的,直接应用比较少见,感兴趣的读者可以查看官网的文档说明。

支持的数据类型

前面示例中的 DataStream,流中的数据类型都是定义好的 POJO 类。如果 DataStream 中的类型是简单的基本类型,还可以直接转换成表吗?这就涉及了 Table 中支持的数据类型。整体来看,DataStream 中支持的数据类型,Table 中也是都支持的,只不过在进行转换时需要注意一些细节。

(1)原子类型
在 Flink 中,基础数据类型(Integer、Double、String)和通用数据类型(也就是不可再拆分的数据类型)统一称作“原子类型”。原子类型的 DataStream,转换之后就成了只有一列的Table,列字段(field)的数据类型可以由原子类型推断出。另外,还可以在 fromDataStream()方法里增加参数,用来重新命名列字段。

StreamTableEnvironment tableEnv = ...;
DataStream<Long> stream = ...;
// 将数据流转换成动态表,动态表只有一个字段,重命名为 myLong
Table table = tableEnv.fromDataStream(stream, $("myLong"));

(2)Tuple 类型
当原子类型不做重命名时,默认的字段名就是“f0”,容易想到,这其实就是将原子类型看作了一元组 Tuple1 的处理结果。

Table 支持 Flink 中定义的元组类型 Tuple,对应在表中字段名默认就是元组中元素的属性名 f0、f1、f2…。所有字段都可以被重新排序,也可以提取其中的一部分字段。字段还可以通过调用表达式的 as()方法来进行重命名。

StreamTableEnvironment tableEnv = ...;
DataStream<Tuple2<Long, Integer>> stream = ...;
// 将数据流转换成只包含 f1 字段的表
Table table = tableEnv.fromDataStream(stream, $("f1"));
// 将数据流转换成包含 f0 和 f1 字段的表,在表中 f0 和 f1 位置交换
Table table = tableEnv.fromDataStream(stream, $("f1"), $("f0"));
// 将 f1 字段命名为 myInt,f0 命名为 myLong
Table table = tableEnv.fromDataStream(stream, $("f1").as("myInt"), $("f0").as("myLong"));

(3)POJO 类型
Flink 也支持多种数据类型组合成的“复合类型”,最典型的就是简单 Java 对象(POJO 类型)。由于 POJO 中已经定义好了可读性强的字段名,这种类型的数据流转换成 Table 就显得无比顺畅了。

将 POJO 类型的 DataStream 转换成 Table,如果不指定字段名称,就会直接使用原始 POJO 类型中的字段名称。POJO 中的字段同样可以被重新排序、提却和重命名,这在之前的例子中已经有过体现。

(4)Row 类型
Flink 中还定义了一个在关系型表中更加通用的数据类型——行(Row),它是 Table 中数据的基本组织形式。Row 类型也是一种复合类型,它的长度固定,而且无法直接推断出每个字段的类型,所以在使用时必须指明具体的类型信息;我们在创建 Table 时调用的 CREATE语句就会将所有的字段名称和类型指定,这在 Flink 中被称为表的“模式结构”(Schema)。除此之外,Row 类型还附加了一个属性 RowKind,用来表示当前行在更新操作中的类型。这样,Row 就可以用来表示更新日志流(changelog stream)中的数据,从而架起了 Flink 中流和表的
转换桥梁。所以在更新日志流中,元素的类型必须是 Row,而且需要调用ofKind()方法来指定更新类型。下面是一个具体的例子:

DataStream<Row> dataStream = env.fromElements(
 Row.ofKind(RowKind.INSERT, "Alice", 12),
 Row.ofKind(RowKind.INSERT, "Bob", 5),
 Row.ofKind(RowKind.UPDATE_BEFORE, "Alice", 12),
 Row.ofKind(RowKind.UPDATE_AFTER, "Alice", 100));
// 将更新日志流转换为表
Table table = tableEnv.fromChangelogStream(dataStream);

全代码展示:

public class TableFlow {
    public static void main(String[] args) throws Exception {
        //1.创建流处理环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        //读取数据源
        SingleOutputStreamOperator<Event> dataStream = env.addSource(new ClickSource())
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ZERO)
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override
                            public long extractTimestamp(Event element, long recordTimestamp) {
                                return element.timestamp;
                            }
                        }));

        //2.创建表环境
        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        //3.将DataStream 转换成 Table
        Table eventTable = tableEnv.fromDataStream(dataStream);

        //4.注册临时表
        tableEnv.createTemporaryView("clickTable",eventTable);

        //5.编写sql操作
        String sql = "select `user`,count(url) as cnt from clickTable group by user";
        String sql1 = "select `user` from clickTable";
        //6.执行sql
        Table aggResult = tableEnv.sqlQuery(sql);
        Table result = tableEnv.sqlQuery(sql);

        //7.输出 (流转换成表)
        tableEnv.toChangelogStream(aggResult).print("agg");
        tableEnv.toDataStream(result).print("result");

        //8.执行
        env.execute();
    }
}

Gitee上的完整代码

你可能感兴趣的:(#,Flink,sql,flink,java)