目录
Flink API概述
两套方案的区别
两者在编译与执行的区别
两者在优化SQL语句时的区别
如何使用Table API
入门
导入依赖
代码结构
创建TableEnvironment
创建表
从外部关系型数据库中创建表
标识符
查询表
使用table API
使用SQL
Table sink将表写到外部系统
与DataStream和DataSet API集成
将table转换成DataStream或DataSet
Data Types映射Table Schema
Explain
早期的flink table API十分不完善,功能很少,如果需要使用的话,基本需要二次开发。所以当时各大厂商很少使用,即便有使用的,也都是自成一套体系,导致flink table API一直处于一种尴尬的地位。直到flink 1.09版本以后,阿里贡献出Blink,table API才逐步完善。
flink支持table API可以使用DDL风格进行开发,支持包含select、filter、where等方法,类似spark的DF编程;也可以直接使用sql语法进行开发。
目前Flink Table API还不完善,不是所有操作都支持DDL风格API和SQL语法与DataSet和DataStream相结合的,这在后面两种方案的差别中会提到。但是后续会逐步完善。
flink的table API有两套方案,分别是Blink和原生flink table API,用于对代码进行编译与优化。根据官网文档,推荐在生产环境中使用原生的table API而非Blink,因为原生的table API更加稳定。
两者区别:
旧方案:
根据table API和SQL的输入的是流数据还是批数据,会转换为DataStream或DataSet程序。Flink在内部分两步将sql语句转换为逻辑执行计划:
在以下情况下,将转换Table API或SQL转换成DataStream或DataSet:
将Table
发送到TableSink
,即当Table.insertInto()
被调用时。TableEnvironment.sqlUpdate()
调用。Table
转换为DataStream
或DataSet
。经过转换,并在调用StreamExecutionEnvironment.execute()
或时执行ExecutionEnvironment.execute()后,
将以常规DataStream或DataSet程序一样处理Table API或SQL的结果。
Blink方案:
无论Table API和SQL的输入是流数据还是批数据,都将转换为DataStream程序。Flink在内部分两步将sql语句转换为逻辑执行计划:
将Table API和SQL转换成DataStream时,对于TableEnvironment
和StreamTableEnvironment有不同的转换方式
。
对于TableEnvironment
,Table API或SQL在TableEnvironment.execute()
被调用时会进行转换,因为TableEnvironment
将优化多个multiple-sinks到一个DAG中。
而对于StreamTableEnvironment,在以下情况下转换表API或SQL查询:
将Table
发送到TableSink
,即当Table.insertInto()
被调用时。TableEnvironment.sqlUpdate()
调用。Table
转换为DataStream
经过转换,并在调用StreamExecutionEnvironment.execute()
或时执行ExecutionEnvironment.execute()后,
将以常规DataStream或DataSet程序一样处理Table API或SQL的结果。
旧方案:
旧方案利用Apache Calcite来优化和翻译SQL。目前执行的优化包括字段映射、谓词下推、subquery decorrelation(笔者译为子查询分解,是一种避免对 inner 表频繁的做 inner 操作的手段)以及其他类型的优化。旧方案尚未优化join的顺序,而是按照SQL中定义的顺序执行。
可以使用一个CalciteConfig
对象,对不同阶段应用的优化方式进行配置。可以通过CalciteConfig.createBuilder()创建该对象
,并通过TableEnvironment 的tableEnv.getConfig.setPlannerConfig(calciteConfig)方法将该对象中的配置应用到环境中
。
Blink:
注意:目前IN/EXISTS/NOT IN/NOT EXISTS只支持优化子查询中的连接条件。
优化器能够做出智能决策,不仅基于优化方案,还会根据source的数据和每个机器的状态(如io、cpu、网络和内存)进行优化。
高级用户可以通过CalciteConfig对象自定义优化方案。
根据使用的编程语言,导入不同的连接器依赖:
org.apache.flink
flink-table-api-java-bridge_2.11
1.10.0
provided
org.apache.flink
flink-table-api-scala-bridge_2.11
1.10.0
provided
根据选用的方案(Blink与原生API)导入不同的依赖,官网推荐使用原生API:
org.apache.flink
flink-table-planner_2.11
1.10.0
provided
org.apache.flink
flink-table-planner-blink_2.11
1.10.0
provided
其他必要依赖:
org.apache.flink
flink-table-common
1.10.0
provided
org.apache.flink
flink-table-common
1.10.0
provided
table API和SQL基于数据流和批的代码结构是一样的,都是创建TableEnvironment,然后通过TableEnvironment获取table,继而对table进行操作。以下是一个大致的代码结构:
// 创建TableEnvironment,根据流数据或批数据创建不同的env
// 具体参考下一节:创建TableEnvironment
val tableEnv = ... // see "Create a TableEnvironment" section
// 创建表
tableEnv.connect(...).createTemporaryTable("table1")
tableEnv.connect(...).createTemporaryTable("outputTable")
// 使用API对表进行转换,获取新的表
val tapiResult = tableEnv.from("table1").select(...)
// 使用SQL对表进行转换,获取新的表
val sqlResult = tableEnv.sqlQuery("SELECT ... FROM table1 ...")
// 将表提交给TableSink,API和SQL的提交方式相同
tapiResult.insertInto("outputTable")
// 执行
tableEnv.execute("scala_job")
TableEnvironment是table API的核心,它负责:
ExecutionEnvironment
或StreamExecutionEnvironment,用于将table转换回流数据或批数据
table绑定于TableEnvironment,基于不同的TableEnvironment的table,不能在一起操作,如join、union等。
使用静态方法BatchTableEnvironment.create()
或StreamTableEnvironment.create()创建
TableEnvironment。方法参数为
StreamExecutionEnvironment
或ExecutionEnvironment
创建流环境还是批环境需要根据项目情况选择。如果环境中存在两种方案的jar,则在创建时需注意使用的是哪个jar包中的方法。
以下是使用两种方案创建流/批环境的示例:
// **********************
// FLINK STREAMING QUERY
// **********************
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.table.api.EnvironmentSettings
import org.apache.flink.table.api.scala.StreamTableEnvironment
val fsSettings = EnvironmentSettings.newInstance().useOldPlanner().inStreamingMode().build()
val fsEnv = StreamExecutionEnvironment.getExecutionEnvironment
val fsTableEnv = StreamTableEnvironment.create(fsEnv, fsSettings)
// or val fsTableEnv = TableEnvironment.create(fsSettings)
// ******************
// FLINK BATCH QUERY
// ******************
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.table.api.scala.BatchTableEnvironment
val fbEnv = ExecutionEnvironment.getExecutionEnvironment
val fbTableEnv = BatchTableEnvironment.create(fbEnv)
// **********************
// BLINK STREAMING QUERY
// **********************
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.table.api.EnvironmentSettings
import org.apache.flink.table.api.scala.StreamTableEnvironment
val bsEnv = StreamExecutionEnvironment.getExecutionEnvironment
val bsSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build()
val bsTableEnv = StreamTableEnvironment.create(bsEnv, bsSettings)
// or val bsTableEnv = TableEnvironment.create(bsSettings)
// ******************
// BLINK BATCH QUERY
// ******************
import org.apache.flink.table.api.{EnvironmentSettings, TableEnvironment}
val bbSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inBatchMode().build()
val bbTableEnv = TableEnvironment.create(bbSettings)
TableEnvironment
维护了一个k-v结构的目录,用于保存哪些被创建的表的标识。每个标识都包含三个部分:catalog , database 以及object (表名或者视图名)。可以设置当前catalog 和当前database,如果未指定catalog和database ,则会使用当前默认的值。如:
// get a TableEnvironment
val tEnv: TableEnvironment = ...;
tEnv.useCatalog("custom_catalog")
tEnv.useDatabase("custom_database")
视图和表:Tables 分别虚拟的VIEWS(视图)和常规的TABLE,视图可以从一个已存在的表对象中创建,通常是一个查询结果。表则是描绘一个外部的数据,如文件或者数据库等。
临时表与永久表:Table分为临时表与永久表,临时表仅在一次Flink session中有效,永久表则可以在不同的Flink session与集群中有效。永久表需要一个存储目录catalog保存表的元数据信息,类似Hive 的Metastore。一旦永久表被创建,则所有已连接到catalog的Flink Session都可以立即读取到它,重启Flink后也可以继续使用永久表,直到它被删除。
另一方面,临时表总是保存在内存里,且仅存在于创建它的那个Flink Session中。对于其他session是不可见的。在创建临时表时可以指定catalog和database ,但是临时表信息不会保存到catalog和database,所以即使删除了临时表所属的database,临时表也不会被删除。
注意:如果创建了一张临时表的标识符和已存在的永久表相同,则临时表会覆盖永久表(逻辑上的覆盖,不会影响数据),也就是说,对这个表的所有操作都是基于临时表的,而非永久表的。可以利用这个机制进行一些测试。
以下是创建一个视图的代码示例:
// 创建 TableEnvironment
val tableEnv = ...
// 使用环境对象从外部表中查询,并将结果创建一张表
val projTable: Table = tableEnv.from("X").select(...)
// 使用表projTable 注册成临时表 "projectedTable"
tableEnv.createTemporaryView("projectedTable", projTable)
Flink也可以从外部的关系型数据库中创建表,使用环境对象可以获取一个连接器来连接数据库,这个连接器描述了存储数据的外部系统,如数据库,或者kafka,或其他文件系统等。
tableEnvironment
.connect(...)
.withFormat(...)
.withSchema(...)
.inAppendMode()
.createTemporaryTable("MyTable")
上面说到,标识符包含三个部分:catalog , database 以及object,当设置了当前的catalog和database时,可以省略这两个参数。也可以在设置了当前catalog和database时指定其他的catalog和database。如果表名是关键字或包含特殊字符,可以使用``引起来。如下列代码:
// 创建 TableEnvironment
val tEnv: TableEnvironment = ...;
tEnv.useCatalog("custom_catalog")
tEnv.useDatabase("custom_database")
// 创建表table
val table: Table = ...;
// 在当前默认的catalog 和database 下创建临时视图:exampleView
tableEnv.createTemporaryView("exampleView", table)
// 在当前默认的catalog 下,创建database为other_database的临时视图:exampleView
tableEnv.createTemporaryView("other_database.exampleView", table)
// 在当前默认的catalog 和database 下创建临时视图:'View',View 是关键字,所以要用``引起来
tableEnv.createTemporaryView("`View`", table)
// 在当前默认的catalog 和database 下创建临时视图: 'example.View' ,因为包含特殊符号".",所以要用``引起来
tableEnv.createTemporaryView("`example.View`", table)
// 创建catalog 为other_catalog,database为other_database的临时视图:exampleView
tableEnv.createTemporaryView("other_catalog.other_database.exampleView", table)
Scala和Java可以使用table API进行查询,与SQL不同,table API不是传入一个sql字符串,而是由API一层一层组成的。
Table API底层实际上是根据表的env类别,基于流数据或批数据API实现的。调用table API的方法,实际上会调用DataStream或DataSet的相关操作。table API每次调用,都会返回一个新的table对象,这个对象代表了对原表进行操作后的结果。
以下是使用table API的一个示例:
// 创建TableEnvironment
val tableEnv = ...
// 创建Orders 表
val orders = tableEnv.from("Orders")
// scala使用table API需要导入隐式转换
import org.apache.flink.api.scala._
import org.apache.flink.table.api.scala._
// 对orders进行计算,将结果付给revenue
val revenue = orders
.filter('cCountry === "FRANCE")
.groupBy('cID, 'cName)
.select('cID, 'cName, 'revenue.sum AS 'revSum)
// 之后可以将表保存到存储系统,后面有例子
// execute query
tableEnv.execute("scala_job")
Flink’s SQL是基于 Apache Calcite的,直接将sql语句定义为一个String即可。
以下代码展示了如何使用sql进行查询,并将结构返回一个table:
// get a TableEnvironment
val tableEnv = ... // see "Create a TableEnvironment" section
// register Orders table
// compute revenue for all customers from France
val revenue = tableEnv.sqlQuery("""
|SELECT cID, cName, SUM(revenue) AS revSum
|FROM Orders
|WHERE cCountry = 'FRANCE'
|GROUP BY cID, cName
""".stripMargin)
// emit or convert Table
// execute query
以下代码展示如何使用sql将查询结果插入到已注册的表中:
// get a TableEnvironment
val tableEnv = ... // see "Create a TableEnvironment" section
// register "Orders" table
// register "RevenueFrance" output table
// compute revenue for all customers from France and emit to "RevenueFrance"
tableEnv.sqlUpdate("""
|INSERT INTO RevenueFrance
|SELECT cID, cName, SUM(revenue) AS revSum
|FROM Orders
|WHERE cCountry = 'FRANCE'
|GROUP BY cID, cName
""".stripMargin)
// execute query
table API和sql可以混合使用,因为它们返回的都是一个table对象。
Flink可以使用Table sink将表写到外部系统,table sink是一个通用的接口,它支持将数据写到各种文件系统(CSV, Apache Parquet, Apache Avro)或者各种存储系统(JDBC, Apache HBase, Apache Cassandra, Elasticsearch)或者消息队列( Apache Kafka, RabbitMQ)。
基于批数据的表只能使用BatchTableSink,基于流数据的表可以使用AppendStreamTableSink,或RetractStreamTableSink,或UpsertStreamTableSink。具体可见Table Sources & Sinks。
Table.insertInto(String tableName)方法可以将表插入到另一个已注册的表中。该方法会在catalog中查找对象表是否存在,并验证两张表的
schema是否相同。
以下代码展示了如何将表写入到:
// 创建 TableEnvironment
val tableEnv = ...
// 创建输出的目标表CsvSinkTable的连接
val schema = new Schema()
.field("a", DataTypes.INT())
.field("b", DataTypes.STRING())
.field("c", DataTypes.LONG())
tableEnv.connect(new FileSystem("/path/to/file"))
.withFormat(new Csv().fieldDelimiter('|').deriveSchema())
.withSchema(schema)
.createTemporaryTable("CsvSinkTable")
// 使用api或sql计算出要插入的数据result
val result: Table = ...
// 将result插入到CsvSinkTable
result.insertInto("CsvSinkTable")
// execute the program
两套方案都可以和DataStream
API集成,也就是说,可以将一个DataStream转换成一个table,也可以将table转换成DataStream。只有原生的原生的方案可以和DataSet API集成。Blink 在基于批数据时,不能与流数据合并处理。 注意:下面关于DataSet的
讨论都是对基于批处理的原生方案进行的。
下面是一个DataStream和Table的相互转换的例子:
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.table.api.scala.StreamTableEnvironment
class Test {
def main(args: Array[String]): Unit = {
val environment = StreamExecutionEnvironment.getExecutionEnvironment
environment.setParallelism(1)
//创建datastream
var host = "192.168.68.131"
var port = 8080
val textDstream: DataStream[String] = environment.socketTextStream(host,port,"\n".charAt(0))
val stream: DataStream[(Long, String)] = textDstream.map(...)
//创建表执行环境
val tableEnvironment = StreamTableEnvironment.create(tableEnvironment)
// 将DataStream转换成临时视图,默认字段名为 "f0", "f1"
tableEnvironment .createTemporaryView("view", stream)
// 将DataStream转换成临时视图,设置字段名为'field1, 'field2
tableEnvironment .createTemporaryView("view2", stream, 'field1', 'field2')
// 将DataStream转换成一张表,默认字段名为 "f0", "f1"
val table = tableEnvironment.fromDataStream(stream)
// 将DataStream转换成一张表,设置字段名为'field1, 'field2'
val table = tableEnvironment.fromDataStream(stream, 'field1', 'field2')
//将table转换成datastream
//需导入隐式转换
import org.apache.flink.api.scala._
val datastream = tableEnvironment.toAppendStream(table)
//执行
tableEnvironment.execute("test")
}
}
Table可以转换成DataStream或DataSet,也就是说,可以将Table API或SQL的执行结果转换成DataStream或DataSet,然后再用DataStream或DataSet的API进行计算。
当使用Table转换成DataStream或DataSet时,需要为DataStream或DataSet指定数据类型。即将table的每一行转换成指定的数据类型。通常情况下会使用最方便的数据类型:Row。以下是一些可选的数据类型:
Integer
, Double
, String
),不支持字段值为null,类型安全。Table作为对DataStream的查询结果,是会被动态更新的,当新的数据到达时,table的内容就会发生改变。也就是说,当将一个动态变化的Table转换成DataStream时,需要指定更新方式。
将Table转换成DataStream时有两种插入方式:
boolean
类型的flag来标记数据是INSERT
还是DELETE
。以下是一个table转DataStream的例子:
// 创建基于流处理的TableEnvironment
val tableEnv: StreamTableEnvironment = ...
// 假设table包含两个字段 (String name, Integer age)
val table: Table = ...
// 将table转换成Append模式的DataStream,数据类型为 Row
val dsRow: DataStream[Row] = tableEnv.toAppendStream[Row](table)
// 将table转换成Append模式的DataStream,数据类型为 Tuple2[String, Int]
val dsTuple: DataStream[(String, Int)] dsTuple =
tableEnv.toAppendStream[(String, Int)](table)
// 将table转换成Retract 模式的DataStream,数据类型为 Row
// 假设数据类型为X,那么DataStream的数据类型应该是: DataStream[(Boolean, X)].
// 这个boolean类型的字段表示数据修改的类型
// True :INSERT, false :DELETE.
val retractStream: DataStream[(Boolean, Row)] = tableEnv.toRetractStream[Row](table)
以下是一个table转DataSet的例子:
// 创建基于批处理的TableEnvironment
val tableEnv = BatchTableEnvironment.create(env)
// 假设table包含两个字段 (String name, Integer age)
val table: Table = ...
// 将table转换成DataSet,数据类型为Row
val dsRow: DataSet[Row] = tableEnv.toDataSet[Row](table)
// 将table转换成DataSet,数据类型为Tuple2[String, Int]
val dsTuple: DataSet[(String, Int)] = tableEnv.toDataSet[(String, Int)](table)
Flink的DataStream 和DataSet 支持多种数据类型。允许使用Tuples ,POJO,Scala的 case classes和Flink的Row等包含嵌套的数据类型,并且可以使用table表达式进行访问,其他的数据类型视为原子类型。下面举例table API是如何将复杂数据结构转换成内部的一行数据,以及DataStream
如何转换成table。
DataStream的数据类型与Table Schema映射有两种方式:基于字段位置或基于字段名。
基于字段位置
基于位置的映射可用于在保持字段顺序的同时为字段提供更有意义的名称。这种映射方式要求数据的字段顺序是固定的,如Tuples 、case classes、ROW。如果是POJO类型的话,则不能使用这种映射方式,必须使用基于字段名的映射方式,这个在下一节会说明。如果使用基于字段位置的映射关系,则不能使用as关键字来定义别名。
当基于字段位置映射数据时,不能指定在DataStream中已存在的字段名,否则API会自动认为是使用基于字段名映射的方式。如果不指定字段名,则字段或复合数据结构中的原子类型数据会使用默认的字段名或f0、f1等。
以下是基于字段位置映射的一个示例:
//创建 TableEnvironment
val tableEnv: StreamTableEnvironment = ...
val stream: DataStream[(Long, Int)] = ...
// 将DataStream 转换成Table ,使用默认字段名 "_1" and "_2"
val table: Table = tableEnv.fromDataStream(stream)
// 将DataStream 转换成Table,只保留第一个字段,且将字段名命名为 "myLong"
val table: Table = tableEnv.fromDataStream(stream, 'myLong')
// 将DataStream 转换成Table ,将字段名按顺序定义为 "myLong" 和"myInt"
val table: Table = tableEnv.fromDataStream(stream, 'myLong, 'myInt)
基于字段名
基于字段名的映射方式适用于任意数据类型包括POJO,这是定义table schema最灵活的映射方式。所有的字段都可以根据字段名进行引用,也可以使用as关键字对字段定义别名。字段可以重新排序。如果没有指定字段名。则会使用默认的字段名,原子数据类型则使用f0、f1之类的默认字段名。
以下是基于字段名映射的一个示例:
// 创建 TableEnvironment
val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section
val stream: DataStream[(Long, Int)] = ...
// 将DataStream转换成Table,使用默认字段名: "_1" and "_2"
val table: Table = tableEnv.fromDataStream(stream)
// 将DataStream转换成Table,只保留字段 "_2"
val table: Table = tableEnv.fromDataStream(stream, '_2')
// 将DataStream转换成Table,并且将两个字段交换位置
val table: Table = tableEnv.fromDataStream(stream, '_2', '_1')
// 将DataStream转换成Table,交换字段位置,且为字段定义别名"myInt" "myLong"
val table: Table = tableEnv.fromDataStream(stream, '_2' as 'myInt, '_1' as 'myLong)
以下都是关于不同数据类型在基于字段名进行映射的情况:
原子类型
flink将原语(Integer
, Double
, String
)或不能再分解的数据类型称之为原子类型。当一个原子类型的DataStream或DataSet转换成只有一个字段的table时,table字段的数据类型会根据原子类型推断出来。并且可以指定字段名。
以下是一个原子类型在基于字段名进行映射的示例:
// 创建 TableEnvironment
val tableEnv: StreamTableEnvironment = ...
val stream: DataStream[Long] = ...
// 将DataStream 转换成Table ,使用默认字段名 "f0"
val table: Table = tableEnv.fromDataStream(stream)
// 将DataStream 转换成Table ,定义字段名为 "myLong"
val table: Table = tableEnv.fromDataStream(stream, 'myLong')
Tuples 或Case Classes
Flink支持Scala的内置元组,并为Java提供了Flink自己的元组类。两种元组的DataStreams和DataSet都可以转换为表。可以通过提供所有字段的名称来重命名字段(基于字段位置进行映射)。如果未指定任何字段名称,则使用默认字段名称。如果原始字段名(flink元祖:f0
,f1
,...scala元祖:_1
,_2
...)被引用时,API会认为值基于字段名进行映射。基于字段名进行映射时,支持使用as关键字定义别名,支持重新定义字段顺序。以下是一个代码示例:
// 创建 TableEnvironment
val tableEnv: StreamTableEnvironment = ...
val stream: DataStream[(Long, String)] = ...
// 将DataStream 转换成Table 使用默认字段名 '_1', '_2'
val table: Table = tableEnv.fromDataStream(stream)
// 将DataStream 转换成Table ,重命名字段名为 "myLong", "myString" (基于字段位置进行映射)
val table: Table = tableEnv.fromDataStream(stream, 'myLong', 'myString')
// 将DataStream 转换成Table ,将两个字段调换位置 "_2", "_1" (基于字段名进行映射)
val table: Table = tableEnv.fromDataStream(stream, '_2', '_1')
// 将DataStream 转换成Table ,只保留字段 "_2" (基于字段名进行映射)
val table: Table = tableEnv.fromDataStream(stream, '_2')
// 将DataStream 转换成Table ,调换字段位置,并重命名字段名为 "myString", "myLong" (基于字段名进行映射)
val table: Table = tableEnv.fromDataStream(stream, '_2 as myString', '_1 as myLong')
// 定义样例类
case class Person(name: String, age: Int)
val streamCC: DataStream[Person] = ...
// 将DataStream 转换成Table ,使用默认的字段名 'name', 'age'
val table = tableEnv.fromDataStream(streamCC)
// 将DataStream 转换成Table ,重命名字段名为 'myName', 'myAge' (基于字段位置进行映射)
val table = tableEnv.fromDataStream(streamCC, 'myName', 'myAge')
// 将DataStream 转换成Table ,调换字段位置,并重命名字段名为 "myAge", "myName" (基于字段名进行映射)
val table: Table = tableEnv.fromDataStream(stream, 'age as myAge', 'name as myName')
POJO(Java和Scala)
当将一个POJO类型的 DataStream
或DataSet
转换成Table时
没有指定字段名,则会使用POJO本身的字段名作为table的字段名。当基于字段名进行映射时需要指定原始名称,并且不能基于字段位置进行映射。可以使用别名as为字段
重命名,支持调换字段位置。
// 创建 TableEnvironment
val tableEnv: StreamTableEnvironment = ...
// Person 是一个包含 "name" 和 "age" 字段的 POJO。
val stream: DataStream[Person] = ...
// 将 DataStream 转换成Table,使用默认的字段名 "age", "name" (字段会按名称排序)
val table: Table = tableEnv.fromDataStream(stream)
// 将 DataStream 转换成Table,并将字段名重定义为 "myAge", "myName" (基于字段名进行映射)
val table: Table = tableEnv.fromDataStream(stream, 'age as myAge', 'name as myName')
// 将 DataStream 转换成Table,仅保留字段 "name" (基于字段名进行映射)
val table: Table = tableEnv.fromDataStream(stream, 'name')
// 将 DataStream 转换成Table,仅保留name字段,并将其重命名为 "myName" (基于字段名进行映射)
val table: Table = tableEnv.fromDataStream(stream, 'name as myName')
ROW
ROW类型支持任意数量的字段,且支持字段为null值,可以通过设置RowTypeInfo
指定字段名,也可以在转换ROW类型的DataStream或DataSet时指定字段名。ROW类型可以通过基于字段位置和基于字段名两种方式进行映射字段。支持为所有字段重新定义字段名(基于字段位置进行映射),也可以指定字段进行保留、排序、重命名(基于字段名进行映射)。
// 创建 TableEnvironment
val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section
// ROW 类型的DataStream,RowTypeInfo指定两个字段名 "name" and "age"
val stream: DataStream[Row] = ...
// 将 DataStream 转换成Table,使用默认字段 "name", "age"
val table: Table = tableEnv.fromDataStream(stream)
// 将 DataStream 转换成Table,并将字段重命名为 "myName", "myAge" (基于字段位置进行映射)
val table: Table = tableEnv.fromDataStream(stream, 'myName', 'myAge')
// 将 DataStream 转换成Table,并将字段重命名为 "myName", "myAge" (基于字段名进行映射)
val table: Table = tableEnv.fromDataStream(stream, 'name as myName', 'age as myAge')
// 将 DataStream 转换成Table, 仅保留字段 "name" (基于字段名进行映射)
val table: Table = tableEnv.fromDataStream(stream, 'name')
// 将 DataStream 转换成Table, 仅保留字段name,并将其重命名为 "myName" (基于字段名进行映射)
val table: Table = tableEnv.fromDataStream(stream, 'name as myName')
代码一:
val env = StreamExecutionEnvironment.getExecutionEnvironment
val tEnv = StreamTableEnvironment.create(env)
val table1 = env.fromElements((1, "hello")).toTable(tEnv, 'count', 'word')
val table2 = env.fromElements((1, "hello")).toTable(tEnv, 'count', 'word')
val table = table1
.where('word'.like("F%"))
.unionAll(table2)
val explanation: String = tEnv.explain(table)
println(explanation)
执行计划:
== Abstract Syntax Tree ==
LogicalUnion(all=[true])
LogicalFilter(condition=[LIKE($1, _UTF-16LE'F%')])
FlinkLogicalDataStreamScan(id=[1], fields=[count, word])
FlinkLogicalDataStreamScan(id=[2], fields=[count, word])
== Optimized Logical Plan ==
DataStreamUnion(all=[true], union all=[count, word])
DataStreamCalc(select=[count, word], where=[LIKE(word, _UTF-16LE'F%')])
DataStreamScan(id=[1], fields=[count, word])
DataStreamScan(id=[2], fields=[count, word])
== Physical Execution Plan ==
Stage 1 : Data Source
content : collect elements with CollectionInputFormat
Stage 2 : Data Source
content : collect elements with CollectionInputFormat
Stage 3 : Operator
content : from: (count, word)
ship_strategy : REBALANCE
Stage 4 : Operator
content : where: (LIKE(word, _UTF-16LE'F%')), select: (count, word)
ship_strategy : FORWARD
Stage 5 : Operator
content : from: (count, word)
ship_strategy : REBALANCE
代码二:
val settings = EnvironmentSettings.newInstance.useBlinkPlanner.inStreamingMode.build
val tEnv = TableEnvironment.create(settings)
val schema = new Schema()
.field("count", DataTypes.INT())
.field("word", DataTypes.STRING())
tEnv.connect(new FileSystem("/source/path1"))
.withFormat(new Csv().deriveSchema())
.withSchema(schema)
.createTemporaryTable("MySource1")
tEnv.connect(new FileSystem("/source/path2"))
.withFormat(new Csv().deriveSchema())
.withSchema(schema)
.createTemporaryTable("MySource2")
tEnv.connect(new FileSystem("/sink/path1"))
.withFormat(new Csv().deriveSchema())
.withSchema(schema)
.createTemporaryTable("MySink1")
tEnv.connect(new FileSystem("/sink/path2"))
.withFormat(new Csv().deriveSchema())
.withSchema(schema)
.createTemporaryTable("MySink2")
val table1 = tEnv.from("MySource1").where("LIKE(word, 'F%')")
table1.insertInto("MySink1")
val table2 = table1.unionAll(tEnv.from("MySource2"))
table2.insertInto("MySink2")
val explanation = tEnv.explain(false)
println(explanation)
执行计划:
== Abstract Syntax Tree ==
LogicalSink(name=[MySink1], fields=[count, word])
+- LogicalFilter(condition=[LIKE($1, _UTF-16LE'F%')])
+- LogicalTableScan(table=[[default_catalog, default_database, MySource1, source: [CsvTableSource(read fields: count, word)]]])
LogicalSink(name=[MySink2], fields=[count, word])
+- LogicalUnion(all=[true])
:- LogicalFilter(condition=[LIKE($1, _UTF-16LE'F%')])
: +- LogicalTableScan(table=[[default_catalog, default_database, MySource1, source: [CsvTableSource(read fields: count, word)]]])
+- LogicalTableScan(table=[[default_catalog, default_database, MySource2, source: [CsvTableSource(read fields: count, word)]]])
== Optimized Logical Plan ==
Calc(select=[count, word], where=[LIKE(word, _UTF-16LE'F%')], reuse_id=[1])
+- TableSourceScan(table=[[default_catalog, default_database, MySource1, source: [CsvTableSource(read fields: count, word)]]], fields=[count, word])
Sink(name=[MySink1], fields=[count, word])
+- Reused(reference_id=[1])
Sink(name=[MySink2], fields=[count, word])
+- Union(all=[true], union=[count, word])
:- Reused(reference_id=[1])
+- TableSourceScan(table=[[default_catalog, default_database, MySource2, source: [CsvTableSource(read fields: count, word)]]], fields=[count, word])
== Physical Execution Plan ==
Stage 1 : Data Source
content : collect elements with CollectionInputFormat
Stage 2 : Operator
content : CsvTableSource(read fields: count, word)
ship_strategy : REBALANCE
Stage 3 : Operator
content : SourceConversion(table:Buffer(default_catalog, default_database, MySource1, source: [CsvTableSource(read fields: count, word)]), fields:(count, word))
ship_strategy : FORWARD
Stage 4 : Operator
content : Calc(where: (word LIKE _UTF-16LE'F%'), select: (count, word))
ship_strategy : FORWARD
Stage 5 : Operator
content : SinkConversionToRow
ship_strategy : FORWARD
Stage 6 : Operator
content : Map
ship_strategy : FORWARD
Stage 8 : Data Source
content : collect elements with CollectionInputFormat
Stage 9 : Operator
content : CsvTableSource(read fields: count, word)
ship_strategy : REBALANCE
Stage 10 : Operator
content : SourceConversion(table:Buffer(default_catalog, default_database, MySource2, source: [CsvTableSource(read fields: count, word)]), fields:(count, word))
ship_strategy : FORWARD
Stage 12 : Operator
content : SinkConversionToRow
ship_strategy : FORWARD
Stage 13 : Operator
content : Map
ship_strategy : FORWARD
Stage 7 : Data Sink
content : Sink: CsvTableSink(count, word)
ship_strategy : FORWARD
Stage 14 : Data Sink
content : Sink: CsvTableSink(count, word)
ship_strategy : FORWARD