Flink Table API & SQL概念和通用API

官网链接:https://ci.apache.org/projects/flink/flink-docs-release-1.9/zh/dev/table/common.html#register-a-datastream-or-dataset-as-table

Table API & SQL概念和通用API

Apache Flink具有两个关系API-Table API和SQL-用于统一流和批处理。Table API是用于Scala和Java的语言集成查询API,它允许以非常直观的方式组合来自关系运算符(例如选择,过滤和联接)的查询。Flink的SQL支持基于实现SQL标准的Apache Calcite。无论输入是批处理输入DataSet还是流输入DataStream,在两个接口中指定的查询都具有相同的语义并指定相同的结果。

Table API和SQL接口以及Flink的DataStream和DataSet API紧密集成在一起。您可以轻松地在所有API和基于API的库之间切换。例如,您可以使用CEP库从DataStream中提取模式,然后使用Table API来分析模式,或者您可以使用SQL查询来扫描,过滤和聚合批处理表,然后在预处理的程序上运行Gelly图算法数据。

注意,Table API和SQL尚未完成功能,正在积极开发中。[Table API,SQL]和[stream,batch]输入的每种组合都不支持所有操作。

Table API & SQL程序结构

用于批处理和流式传输的所有Table API和SQL程序都遵循相同的模式。以下代码示例显示了Table API和SQL程序的通用结构。

// step1 : 为特定的执行计划批处理或流创建一个TableEnvironment
val tableEnv = ... // see "Create a TableEnvironment" section

// step2 : 注册表 
tableEnv.registerTable("table1", ...)           // or
tableEnv.registerTableSource("table2", ...)     // or
tableEnv.registerExternalCatalog("extCat", ...)
// step 3 : 注册输出表
tableEnv.registerTableSink("outputTable", ...);

// step 4 : 查询API
val tapiResult = tableEnv.scan("table1").select(...)
// create a Table from a SQL query
val sqlResult  = tableEnv.sqlQuery("SELECT ... FROM table2 ...")

// step 5 : 将Table API结果表发送到TableSink,与SQL结果相同
tapiResult.insertInto("outputTable")

// step 6 : 执行程序
tableEnv.execute("scala_job")

注意:Table API和SQL查询可以轻松地与DataStream或DataSet程序集成并嵌入其中。请参阅与DataStream和DataSet API集成,以了解如何将DataStream和DataSet转换为Tables,反之亦然。

创建一个TableEnvironment

TableEnvironment是Table API和SQL集成的中心概念。它负责:

  • 在内部Catalog中注册Table
  • 注册外部Catalog
  • 执行SQL查询
  • 注册用户定义的(scalar, table, or aggregation)函数
  • 将DataStream或DataSet转换为Table
  • 持有对ExecutionEnvironment或StreamExecutionEnvironment的引用

Table始终绑定到特定的TableEnvironment。不可能在同一查询中组合不同TableEnvironments的表,例如,将它们join或union。

通过调用带有StreamExecutionEnvironment或ExecutionEnvironment和可选TableConfig的静态BatchTableEnvironment.create()或StreamTableEnvironment.create()方法来创建TableEnvironment。TableConfig可用于配置TableEnvironment或自定义查询优化和转换过程(请参阅查询优化)。

确保选择与您的编程语言匹配的特定计划器BatchTableEnvironment / StreamTableEnvironment。如果两个计划程序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)

注意:如果lib目录中只有一个planner jar,则可以使用useAnyPlanner来创建特定的EnvironmentSettings。

在Catalog注册表

Catalog:所有对数据库和表的元数据信息都存放在Flink CataLog内部目录结构中,其存放了flink内部所有与Table相关的元数据信息,包括表结构信息/数据源信息等。

TableEnvironment维护按名称注册的表的Catalog。表有两种类型,输入表和输出表。可以在Table API和SQL查询中引用输入表并提供输入数据。输出表可用于将表API或SQL查询的结果发送到外部系统。

输入表可以从各种来源进行注册:

  • 现有的Table对象,通常是Table API或SQL查询的结果。
  • 一个TableSource,用于访问外部数据,例如文件,数据库或消息传递系统。
  • DataStream(仅适用于流作业)或DataSet(仅适用于从旧计划程序转换的批处理作业)程序中的DataStream或DataSet。

可以使用TableSink注册输出表。

注册Table

在TableEnvironment中注册一个Table,如下所示:

// get a TableEnvironment
val tableEnv = ... // see "Create a TableEnvironment" section

// table is the result of a simple projection query 
val projTable: Table = tableEnv.scan("X").select(...)

// register the Table projTable as table "projectedTable"
tableEnv.registerTable("projectedTable", projTable)

注意:已注册Table的处理方式与关系数据库系统中已知的VIEW相似,即定义表的查询未进行优化,但当另一个查询引用已注册表时将内联。如果多个查询引用同一个注册表,则将为每个引用查询内联该表并执行多次,即将不会共享注册表的结果。

注册TableSource

通过TableSource,可以访问存储在存储系统中的外部数据,例如数据库(MySQL,HBase等),具有特定编码的文件(CSV,Apache [Parquet,Avro,ORC]等)或消息传递系统(Apache Kafka,RabbitMQ等)。

Flink旨在为常见的数据格式和存储系统提供TableSources。请查看“[Table Sources and Sinks]”页面,以获取受支持的表源的列表以及如何构建自定义表源的说明。

TableSource在TableEnvironment中注册如下:

// get a TableEnvironment
val tableEnv = ... // see "Create a TableEnvironment" section

// create a TableSource
val csvSource: TableSource = new CsvTableSource("/path/to/file", ...)

// register the TableSource as table "CsvTable"
tableEnv.registerTableSource("CsvTable", csvSource)

注意:用于Blink执行计划程序的TableEnvironment仅接受StreamTableSource,LookupableTableSource和InputFormatTableSource,并且用于批处理Blink计划程序的StreamTableSource必须是有界的。

注册TableSink

注册的TableSink可用于将Table API或SQL查询的结果发送到外部存储系统,例如数据库,KV存储,消息队列或文件系统(采用不同的编码,例如CSV,Apache [Parquet,Avro,ORC],…)。

Flink旨在为常见的数据格式和存储系统提供TableSink。请参阅有关“表源和接收器”页面的文档,以获取有关可用接收器的详细信息以及如何实现自定义TableSink的说明。

TableSink在TableEnvironment中注册如下:

// get a TableEnvironment
val tableEnv = ... // see "Create a TableEnvironment" section

// create a TableSink
val csvSink: TableSink = new CsvTableSink("/path/to/file", ...)

// define the field names and types
val fieldNames: Array[String] = Array("a", "b", "c")
val fieldTypes: Array[TypeInformation[_]] = Array(Types.INT, Types.STRING, Types.LONG)

// register the TableSink as table "CsvSinkTable"
tableEnv.registerTableSink("CsvSinkTable", fieldNames, fieldTypes, csvSink)

注册外部Catalog

外部catalog可以提供有关外部数据库和表的信息,例如它们的名称,架构,统计信息,以及有关如何访问存储在外部数据库,表或文件中的数据的信息。

可以通过实现ExternalCatalog接口来创建外部目录,并在TableEnvironment中对其进行注册,如下所示:

// get a TableEnvironment
val tableEnv = ... // see "Create a TableEnvironment" section

// create an external catalog
val catalog: ExternalCatalog = new InMemoryExternalCatalog

// register the ExternalCatalog catalog
tableEnv.registerExternalCatalog("InMemCatalog", catalog)

在TableEnvironment中注册后,可以通过指定表的完整路径(例如catalog.database.table)从Table API或SQL查询中访问在ExternalCatalog中定义的所有表。

当前,Flink提供了一个InMemoryExternalCatalog用于演示和测试。但是,也可以使用ExternalCatalog接口将HCatalog或Metastore之类的目录连接到Table API。

注意:blink执行计划不支持外部目录。

Query a Table

Table API

Table API是用于Scala和Java的语言集成查询API。与SQL相比,查询未指定为字符串,而是以宿主语言逐步构成。

该API基于Table类,Table类代表一个表(流式或批处理),并提供应用关系操作的方法。这些方法返回一个新的Table对象,该对象表示对输入Table应用关系操作的结果。某些关系操作由多个方法调用组成,例如table.groupBy(…)、select();其中groupBy(…)指定表的分组,并select(…)在分组的投影表。

Table API文档描述了流表和批处理表支持的所有Table API操作。

以下示例显示了一个简单的Table API聚合查询:

// get a TableEnvironment
val tableEnv = ... // see "Create a TableEnvironment" section

// register Orders table

// scan registered Orders table
val orders = tableEnv.scan("Orders")
// compute revenue for all customers from France
val revenue = orders
  .filter('cCountry === "FRANCE")
  .groupBy('cID, 'cName)
  .select('cID, 'cName, 'revenue.sum AS 'revSum)

// emit or convert Table
// execute query

注意:Scala Table API使用Scala符号,该符号以一个勾号(’)开头来引用表的属性。Table API使用Scala隐式。确保导入org.apache.flink.api.scala.和org.apache.flink.table.api.scala._以便使用Scala隐式转换。

SQL API

Flink的SQL集成基于实现SQL标准的Apache Calcite。SQL查询被指定为常规字符串。SQL文档描述了Flink对流表和批处理表的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

下面的示例演示如何指定将查询结果插入已注册表的更新查询:

// 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

混合表API和SQL

表API和SQL查询可以轻松混合,因为它们都返回Table对象:

  • 可以在SQL查询返回的Table对象上定义Table API查询。
  • 通过在TableEnvironment中注册结果表并在SQL查询的FROM子句中引用它,可以对Table API查询的结果定义SQL查询。

提交Table

通过将表写入TableSink来提交Table。TableSink是通用接口,用于支持各种文件格式(例如CSV,Apache Parquet,Apache Avro),存储系统(例如JDBC,Apache HBase,Apache Cassandra,Elasticsearch)或消息传递系统(例如Apache Kafka,RabbitMQ)。

批处理表只能写入BatchTableSink,而流式表则需要AppendStreamTableSink,RetractStreamTableSink或UpsertStreamTableSink。

请参阅有关Table Sources & Sinks的文档,以获取有关可用接收器的详细信息以及有关如何实现自定义TableSink的说明。

Table.insertInto(String tableName)方法将Table提交到已注册的TableSink。该方法通过名称从目录中查找TableSink,并验证Table的schema与TableSink的schema是否相同。

以下示例显示如何发出表:

// get a TableEnvironment
val tableEnv = ... // see "Create a TableEnvironment" section

// create a TableSink
val sink: TableSink = new CsvTableSink("/path/to/file", fieldDelim = "|")

// register the TableSink with a specific schema
val fieldNames: Array[String] = Array("a", "b", "c")
val fieldTypes: Array[TypeInformation] = Array(Types.INT, Types.STRING, Types.LONG)
tableEnv.registerTableSink("CsvSinkTable", fieldNames, fieldTypes, sink)

// compute a result Table using Table API operators and/or SQL queries
val result: Table = ...

// emit the result Table to the registered TableSink
result.insertInto("CsvSinkTable")

// execute the program

翻译和执行查询

对于两个执行计划来说,翻译和执行查询的行为是不同的。

根据Table API和SQL查询的输入是流输入还是批处理输入,它们将翻译为DataStream或DataSet程序。查询在内部表示为逻辑查询计划,并分为两个阶段:

  1. 优化逻辑计划
  2. 转换为DataStream或DataSet程序

在以下情况下,将翻译Table API或SQL查询:

  • Table被发送到TableSink,即当调用Table.insertInto() 时。
  • 指定SQL更新查询,即在调用TableEnvironment.sqlUpdate() 时。
  • 将Table转换为DataStream或DataSet(请参阅Integration with DataStream and DataSet API)。

翻译后,将像常规DataStream或DataSet程序一样处理Table API或SQL查询,并在调用StreamExecutionEnvironment.execute() 或ExecutionEnvironment.execute() 时执行。

与DataStream和DataSet API集成

流上的两个执行计划都可以与DataStream API集成。只有旧的执行计划程序才能与DataSet API集成,Blink与Batch执行计划程序不能与两者结合。

注意:下面讨论的DataSet API仅与批量使用的旧计划程序有关。

Table API和SQL查询可以轻松地与DataStream和DataSet程序集成并嵌入其中。例如,可以查询外部表(例如从RDBMS),进行一些预处理,例如过滤,投影,聚合或与元数据联接,然后进一步使用DataStream或DataSet API(以及在这些API之上构建的任何库,例如CEP或Gelly)。相反,也可以将Table API或SQL查询应用于DataStream或DataSet程序的结果。

可以通过将DataStream或DataSet转换为Table来实现这种交互,反之亦然。

Scala的隐式转换

Scala Table API具有对DataSet,DataStream和Table类的隐式转换。通过为Scala DataStream API导入org.apache.flink.table.api.scala.包以及org.apache.flink.api.scala._包,可以启用这些转换。

将DataStream或DataSet注册为Table

可以在TableEnvironment中将DataStream或DataSet注册为表。结果表的模式取决于已注册的DataStream或DataSet的数据类型。

// get TableEnvironment 
// registration of a DataSet is equivalent
val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section

val stream: DataStream[(Long, String)] = ...

// register the DataStream as Table "myTable" with fields "f0", "f1"
tableEnv.registerDataStream("myTable", stream)

// register the DataStream as table "myTable2" with fields "myLong", "myString"
tableEnv.registerDataStream("myTable2", stream, 'myLong, 'myString)

注意:DataStream表的名称不得与^ DataStreamTable [0-9] +模式匹配,并且DataSet表的名称不得与^ DataSetTable [0-9] +模式匹配。这些模式仅供内部使用。

将DataStream或DataSet转换为Table

除了在TableEnvironment中注册DataStream或DataSet之外,还可以将其直接转换为Table。如果要在Table API查询中使用Table,这将很方便。

// get TableEnvironment
// registration of a DataSet is equivalent
val tableEnv = ... // see "Create a TableEnvironment" section

val stream: DataStream[(Long, String)] = ...

// convert the DataStream into a Table with default fields '_1, '_2
val table1: Table = tableEnv.fromDataStream(stream)

// convert the DataStream into a Table with fields 'myLong, 'myString
val table2: Table = tableEnv.fromDataStream(stream, 'myLong, 'myString)

将Table转换为DataStream或DataSet

将Table转换为DataStream或DataSet时,您需要指定结果DataStream或DataSet的数据类型,即要将表的行转换为的数据类型。最方便的转换类型通常是Row。以下列表概述了不同选项的功能:

  • ROW:字段按位置,任意数量的字段进行映射,支持空值,没有类型安全的访问。
  • POJO:字段按名称映射(POJO字段必须命名为Table字段),任意数量的字段,支持空值,类型安全访问。
  • Case Class:字段按位置映射,不支持空值,类型安全访问。
  • Tuple:按位置映射字段,限制为22(Scala)或25(Java)字段,不支持空值,类型安全访问。
  • Atomic Type:表必须具有单个字段,不支持空值,类型安全访问。

将Table转换为DataStream

流式查询结果产生的Table将动态更新,即随着新记录到达查询的输入流中而不断变化。因此,将这种动态查询转换成的DataStream需要对表的更新进行编码。

有两种模式可以将Table转换为DataStream:

1. Append Mode:仅当动态表仅通过INSERT更改进行修改时才可以使用此模式,即它仅是追加操作,并且以前发出的结果从不更新。

2. Retract Mode:始终可以使用此模式。它使用布尔标志对INSERT和DELETE更改进行编码。

// get TableEnvironment. 
// registration of a DataSet is equivalent
val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section

// Table with two fields (String name, Integer age)
val table: Table = ...

// convert the Table into an append DataStream of Row
val dsRow: DataStream[Row] = tableEnv.toAppendStream[Row](table)

// convert the Table into an append DataStream of Tuple2[String, Int]
val dsTuple: DataStream[(String, Int)] dsTuple = 
  tableEnv.toAppendStream[(String, Int)](table)

// convert the Table into a retract DataStream of Row.
//   A retract stream of type X is a DataStream[(Boolean, X)]. 
//   The boolean field indicates the type of the change. 
//   True is INSERT, false is DELETE.
val retractStream: DataStream[(Boolean, Row)] = tableEnv.toRetractStream[Row](table)

注意:有关动态表及其属性的详细讨论,请参见“Dynamic Tables”文档。

将Table转换为DataSet

将Tabble转换为DataSet,如下所示:

// get TableEnvironment 
// registration of a DataSet is equivalent
val tableEnv = BatchTableEnvironment.create(env)

// Table with two fields (String name, Integer age)
val table: Table = ...

// convert the Table into a DataSet of Row
val dsRow: DataSet[Row] = tableEnv.toDataSet[Row](table)

// convert the Table into a DataSet of Tuple2[String, Int]
val dsTuple: DataSet[(String, Int)] = tableEnv.toDataSet[(String, Int)](table)

数据类型到Table的映射

Flink的DataStream和DataSet API支持多种类型。Tuples(内置Scala和Flink Java元组),POJO,Scala case class和Flink的Row类型等复合类型允许嵌套的数据结构具有多个字段,可以在Table表达式中进行访问。其他类型被视为原子类型。在下面,我们描述Table API如何将这些类型转换为内部行表示形式,并显示将DataStream转换为Table的示例。

数据类型到Table模式的映射可以通过两种方式发生:基于字段位置或基于字段名称。

基于位置的映射

基于位置的映射可用于在保持字段顺序的同时为字段赋予更有意义的名称。此映射可用于具有定义的字段顺序的复合数据类型以及原子类型。元组,行和案例类等复合数据类型具有这样的字段顺序。但是,必须根据字段名称映射POJO的字段。可以将字段投影出来,但不能使用别名as重命名。

在定义基于位置的映射时,输入数据类型中不得存在指定的名称,否则API会假定映射应基于字段名称进行。如果未指定任何字段名称,则使用复合类型的默认字段名称和字段顺序,或者原子类型使用f0。

// get a TableEnvironment
val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section

val stream: DataStream[(Long, Int)] = ...

// convert DataStream into Table with default field names "_1" and "_2"
val table: Table = tableEnv.fromDataStream(stream)

// convert DataStream into Table with field "myLong" only
val table: Table = tableEnv.fromDataStream(stream, 'myLong)

// convert DataStream into Table with field names "myLong" and "myInt"
val table: Table = tableEnv.fromDataStream(stream, 'myLong, 'myInt)

基于名称的映射

基于名称的映射可用于任何数据类型,包括POJO。这是定义表模式映射的最灵活的方法。映射中的所有字段均按名称引用,并且可以使用别名as重命名。字段可以重新排序和投影。

如果未指定任何字段名称,则使用复合类型的默认字段名称和字段顺序,或者原子类型使用f0。

// get a TableEnvironment
val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section

val stream: DataStream[(Long, Int)] = ...

// convert DataStream into Table with default field names "_1" and "_2"
val table: Table = tableEnv.fromDataStream(stream)

// convert DataStream into Table with field "_2" only
val table: Table = tableEnv.fromDataStream(stream, '_2)

// convert DataStream into Table with swapped fields
val table: Table = tableEnv.fromDataStream(stream, '_2, '_1)

// convert DataStream into Table with swapped fields and field names "myInt" and "myLong"
val table: Table = tableEnv.fromDataStream(stream, '_2 as 'myInt, '_1 as 'myLong)

原子类型

Flink将基元(Integer, Double, String)或通用类型(无法分析和分解的类型)视为原子类型。原子类型的DataStream或DataSet转换为具有单个属性的表。从原子类型推断出属性的类型,并且可以指定属性的名称。

// get a TableEnvironment
val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section

val stream: DataStream[Long] = ...

// convert DataStream into Table with default field name "f0"
val table: Table = tableEnv.fromDataStream(stream)

// convert DataStream into Table with field name "myLong"
val table: Table = tableEnv.fromDataStream(stream, 'myLong)

Tuples(Scala和Java)和Case Classes(仅Scala)

Flink支持Scala的内置元组,并为Java提供了自己的元组类。两种元组的DataStreams和DataSet都可以转换为表。可以通过提供所有字段的名称来重命名字段(根据位置进行映射)。如果未指定任何字段名称,则使用默认字段名称。如果引用了原始字段名称(Flink元组为f0,f1,…,Scala元组为_1,_2,…),则API会假定映射是基于名称的,而不是基于位置的。基于名称的映射允许使用别名(as)对字段和投影进行重新排序。

// get a TableEnvironment
val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section

val stream: DataStream[(Long, String)] = ...

// convert DataStream into Table with renamed default field names '_1, '_2
val table: Table = tableEnv.fromDataStream(stream)

// convert DataStream into Table with field names "myLong", "myString" (position-based)
val table: Table = tableEnv.fromDataStream(stream, 'myLong, 'myString)

// convert DataStream into Table with reordered fields "_2", "_1" (name-based)
val table: Table = tableEnv.fromDataStream(stream, '_2, '_1)

// convert DataStream into Table with projected field "_2" (name-based)
val table: Table = tableEnv.fromDataStream(stream, '_2)

// convert DataStream into Table with reordered and aliased fields "myString", "myLong" (name-based)
val table: Table = tableEnv.fromDataStream(stream, '_2 as 'myString, '_1 as 'myLong)

// define case class
case class Person(name: String, age: Int)
val streamCC: DataStream[Person] = ...

// convert DataStream into Table with default field names 'name, 'age
val table = tableEnv.fromDataStream(streamCC)

// convert DataStream into Table with field names 'myName, 'myAge (position-based)
val table = tableEnv.fromDataStream(streamCC, 'myName, 'myAge)

// convert DataStream into Table with reordered and aliased fields "myAge", "myName" (name-based)
val table: Table = tableEnv.fromDataStream(stream, 'age as 'myAge, 'name as 'myName)

POJO (Java and Scala)

Flink支持POJO作为复合类型。确定POJO的规则在此处记录。

在不指定字段名称的情况下将POJO DataStream或DataSet转换为Table时,将使用原始POJO字段的名称。名称映射需要原始名称,并且不能按职位进行映射。可以使用别名(使用as关键字)对字段进行重命名,重新排序和投影。

// get a TableEnvironment
val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section

// Person is a POJO with field names "name" and "age"
val stream: DataStream[Person] = ...

// convert DataStream into Table with default field names "age", "name" (fields are ordered by name!)
val table: Table = tableEnv.fromDataStream(stream)

// convert DataStream into Table with renamed fields "myAge", "myName" (name-based)
val table: Table = tableEnv.fromDataStream(stream, 'age as 'myAge, 'name as 'myName)

// convert DataStream into Table with projected field "name" (name-based)
val table: Table = tableEnv.fromDataStream(stream, 'name)

// convert DataStream into Table with projected and renamed field "myName" (name-based)
val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName)

Row

Row数据类型支持任意数量的字段和具有空值的字段。可以通过RowTypeInfo或在将Row DataStream或DataSet转换为Table时指定字段名称。行类型支持按位置和名称映射字段。可以通过提供所有字段的名称(基于位置的映射)来重命名字段,也可以为投影/排序/重新命名(基于名称的映射)单独选择字段。

// get a TableEnvironment
val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section

// DataStream of Row with two fields "name" and "age" specified in `RowTypeInfo`
val stream: DataStream[Row] = ...

// convert DataStream into Table with default field names "name", "age"
val table: Table = tableEnv.fromDataStream(stream)

// convert DataStream into Table with renamed field names "myName", "myAge" (position-based)
val table: Table = tableEnv.fromDataStream(stream, 'myName, 'myAge)

// convert DataStream into Table with renamed fields "myName", "myAge" (name-based)
val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName, 'age as 'myAge)

// convert DataStream into Table with projected field "name" (name-based)
val table: Table = tableEnv.fromDataStream(stream, 'name)

// convert DataStream into Table with projected and renamed field "myName" (name-based)
val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName)

查询优化

Apache Flink利用Apache Calcite来优化和翻译查询。当前执行的优化包括projection和filter push-down,子查询去相关以及其他类型的查询重写。Old Planner尚未优化联接的顺序,而是按照查询中定义的顺序执行它们(FROM子句中的表顺序和/或WHERE子句中的连接谓词顺序)。

通过提供CalciteConfig对象,可以调整在不同阶段应用的优化规则集。可以通过构建器调用CalciteConfig.createBuilder() 来创建此属性,并通过调用tableEnv.getConfig.setPlannerConfig(calciteConfig)将其提供给TableEnvironment。

Explaining a Table

Table API提供了一种机制来解释计算Table的逻辑和优化查询计划。这是通过TableEnvironment.explain(table)方法或TableEnvironment.explain()方法完成的。explain(table)返回给定Table的计划。explain()返回多接收器计划的结果,主要用于Blink计划器。它返回一个描述三个计划的字符串:

  1. 关系查询的抽象语法树,即未优化的逻辑查询计划,
  2. 优化的逻辑查询计划,
  3. 以及实际执行计划。

以下代码显示了一个示例以及使用explain(table)给定Table的相应输出的执行计划:

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

以下代码显示了一个示例以及使用explain() 的多sink相应输出的执行计划:

val settings = EnvironmentSettings.newInstance.useBlinkPlanner.inStreamingMode.build
val tEnv = TableEnvironment.create(settings)

val fieldNames = Array("count", "word")
val fieldTypes = Array[TypeInformation[_]](Types.INT, Types.STRING)
tEnv.registerTableSource("MySource1", new CsvTableSource("/source/path1", fieldNames, fieldTypes))
tEnv.registerTableSource("MySource2", new CsvTableSource("/source/path2",fieldNames, fieldTypes))
tEnv.registerTableSink("MySink1", new CsvTableSink("/sink/path1").configure(fieldNames, fieldTypes))
tEnv.registerTableSink("MySink2", new CsvTableSink("/sink/path2").configure(fieldNames, fieldTypes))

val table1 = tEnv.scan("MySource1").where("LIKE(word, 'F%')")
table1.insertInto("MySink1")

val table2 = table1.unionAll(tEnv.scan("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
发布了87 篇原创文章 · 获赞 69 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/lp284558195/article/details/104247788