Flink Table API & SQL - 概念和通用API

Table API 和 SQL 集成在一個 API 中。這個 API 用作查詢、輸入和輸出的表。本文檔展示了帶有 Table API 和 SQL 查詢的程序的公共結構、如何註冊表、如何查詢表以及如何寫入表。

目錄

兩個Planner之間的主要區別

Table API and SQL的結構

創建一個TableEnvironment

在 Calalog 中註冊表

註冊 Table

註冊 TableSource

註冊 TableSink

註冊外部Calalog

查詢表

Table API

SQL

混合使用 Table API & SQL

寫表

轉化並執行查詢

Old Planner

Blink Planner

與DataStream和DataSet API集成

Scala的隱式轉換

將DataStream和DataSet註冊爲表

將DataStream和DataSet轉換爲表

將錶轉換爲DataStream或DataSet

將錶轉換爲DataStream

將錶轉換爲DataSet

數據類型到表模式的映射

基於位置映射

基於名稱映射

原子類型

元組(Scala和Java)和Case類(僅限Scala)

POJO (Java and Scala)

Row

優化查詢

解釋表


 

兩個Planner之間的主要區別

  1. Blink 將批作業看作一個特殊的流。因此,Table 和 DataSet 之間不能轉換,批處理作業不會被轉換爲 DateSet 程序,而是轉換爲 DataStream 程序,與流作業相同。
  2. Blink planner 不支持 BatchTableSource,所以要使用 StreamTableSource。
  3. Blink planner 支持新的 Catalog,不支持 ExternalCatalog。
  4. 舊 planner 和 Blink planner 的 FilterableTableSource 實現是互不兼容的,舊的把 PlannerExpressions 下發給 FilterableTableSource,Blink planner 下發給 Expressions。
  5. 基於字符串的鍵值配置選項僅用於 Blink planner,詳情參閱://TODO。
  6. 兩個 planner 中 PlannerConfig 的實現(CalciteConfig)是不同的。
  7. Blink planner 將優化多個 sink 到一個 DAG 中(只支持 TableEnvironment)。舊 planner 一個 sink 對應一個新的 DAG,每個 DAG 都是相對獨立的。
  8. 舊 planner 不支持 catalog,Blink planner支持。

Table API and SQL的結構

所有用於批處理和流處理的 Table API & SQL 程序都遵循相同的模式。下面的代碼示例顯示了 Table API & SQL 程序的公共結構。

// create a TableEnvironment for specific planner batch or streaming
val tableEnv = ... // see "Create a TableEnvironment" section
 
// register a Table
tableEnv.registerTable("table1", ...)           // or
tableEnv.registerTableSource("table2", ...)     // or
tableEnv.registerExternalCatalog("extCat", ...)
// register an output Table
tableEnv.registerTableSink("outputTable", ...);
 
// create a Table from a Table API query
val tapiResult = tableEnv.scan("table1").select(...)
// create a Table from a SQL query
val sqlResult  = tableEnv.sqlQuery("SELECT ... FROM table2 ...")
 
// emit a Table API result Table to a TableSink, same for SQL result
tapiResult.insertInto("outputTable")
 
// execute
tableEnv.execute("scala_job")

注意:Table API & SQL 查詢可以很容易地與 DataStream 或 DataSet 程序集成並嵌入其中。查閱下方:與DataStream和DataSet API集成,瞭解如何將數據流和數據集轉換爲表。

創建一個TableEnvironment

TableEnvironment 是 Table API & SQL 集成的核心概念。負責::

  • 在內部 catalog 中註冊表
  • 註冊catalog
  • 執行 SQL 查詢
  • 註冊用戶自定義函數(scalar, table, or aggregation)
  • 將 DataStream 或 DataSet 轉換爲表
  • 保存對 ExecutionEnvironment 或 StreamExecutionEnvironment 的引用

 表總是綁定在特定的 TableEnvironment 上,在同一個查詢中組合不同 TableEnvironment 的表是不可能的,例如,join 或 union 它們。

使用 StreamExecutionEnvironment 或 ExecutionEnvironment 和可選的 TableConfig 調用靜態 BatchTableEnvironment.create()或 StreamTableEnvironment .create()方法創建 TableEnvironment。TableConfig 可以用來配置 TableEnvironment 或 自定義優化查詢(詳情見下:查詢優化

BatchTableEnvironment/StreamTableEnvironment 指定匹配的 planner。

如果兩個 planner 的jar都存在,則需要在程序中顯式指定 planner,如下:

// **********************
// 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(use_any_planner for python)創建特定的 EnvironmentSettings

在 Calalog 中註冊表

TableEnvironment 維護按名稱註冊的表的目錄。有兩種類型的表,輸入表和輸出表。輸入表可以在 Table API & SQL 查詢中引用,並提供輸入數據。輸出表可用於將 Table API & SQL 查詢的結果輸出到外部系統。

輸入表的註冊支持各種各樣的數據源:

  • 現有表對象,通常是 Table API & SQL 查詢的結果。
  • 一個 TableSource,訪問外部數據,例如一個文件,數據庫,消息系統。
  • DataStream(僅用於流任務) 和 DataSet(僅用於從舊 planner 轉換的批作業) 程序中的 DataStream 和 DataSet。註冊 DataStream 和 DataSet 查閱下方:與DataStream和DataSet API集成

使用 TableSink 註冊輸入表。

註冊 Table


 
  1. // get a TableEnvironment

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

  3.  
  4. // table is the result of a simple projection query

  5. val projTable: Table = tableEnv.scan("X").select(...)

  6.  
  7. // register the Table projTable as table "projectedTable"

  8. tableEnv.registerTable("projectedTable", projTable)

  9.  

 注意:註冊表的處理類似於關係數據庫系統中的視圖,即,定義表的查詢未進行優化,但當另一個查詢引用已註冊表時,將內聯該查詢。如果多個查詢引用了相同註冊表,它將爲每個引用查詢內聯並執行多次,即,已註冊表的結果將不會共享。 

註冊 TableSource

TableSource提供了訪問外部數據,諸如,數據庫(MySQL, HBase, …),特定編碼的文件(CSV, Apache [Parquet, Avro, ORC], …),消息系統(Apache Kafka, RabbitMQ, …)。

Flink 旨在爲常見的數據格式和存儲系統提供 TableSources。查閱://TODO,瞭解支持的 Table Source 列表及如何構建自定義 Table Source。


 
  1. // get a TableEnvironment

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

  3.  
  4. // create a TableSource

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

  6.  
  7. // register the TableSource as table "CsvTable"

  8. tableEnv.registerTableSource("CsvTable", csvSource)

注意:用於 Blink planner 的 TableEnvironment 只接受 StreamTableSource、LookupableTableSource 和 InputFormatTableSource,並且必須綁定用於批量 Blink planner 的流表資源。 

註冊 TableSink

已註冊的 TableSink 可以用於將 Table API & SQL 查詢的結果輸出到外部存儲系統,諸如,數據庫(MySQL, HBase, …),特定編碼的文件(CSV, Apache [Parquet, Avro, ORC], …),消息系統(Apache Kafka, RabbitMQ, …)。


 
  1. // get a TableEnvironment

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

  3.  
  4. // create a TableSink

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

  6.  
  7. // define the field names and types

  8. val fieldNames: Array[String] = Array("a", "b", "c")

  9. val fieldTypes: Array[TypeInformation[_]] = Array(Types.INT, Types.STRING, Types.LONG)

  10.  
  11. // register the TableSink as table "CsvSinkTable"

  12. tableEnv.registerTableSink("CsvSinkTable", fieldNames, fieldTypes, csvSink)

註冊外部Calalog

外部目錄可以提供關於外部數據庫和表的信息,比如它們的名稱、模式、統計信息,以及如何訪問存儲在外部數據庫、表或文件中的數據的信息。

外部目錄可以通過實現 ExternalCatalog 接口創建,並在 TableEnvironment 中註冊樣例如下:


 
  1. // get a TableEnvironment

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

  3.  
  4. // create an external catalog

  5. val catalog: ExternalCatalog = new InMemoryExternalCatalog

  6.  
  7. // register the ExternalCatalog catalog

  8. tableEnv.registerExternalCatalog("InMemCatalog", catalog)

在 TableEnvironment 中註冊後,可以通過指定 Table API 或 SQL 查詢的完整路徑(如:catalog.database.table)訪問 ExternalCatalog 中定義的所有表。

目前,Flink 提供了一個用於演示和測試目的的 InMemoryExternalCatalog。然而,ExternalCatalog 接口還可以用於將 HCatalog 或 Metastore 之類的目錄連接到 Table API。

注意:Blink planner 不支持外部目錄

查詢表

Table API

Table API 是一個用於Scala和Java的語言集成查詢 API。與 SQL 相反,查詢沒有指定爲sql,而是在程序中逐步拼接成。

Table API 基於 Table 類, 表示一個表(流或批處理),並提供應用關係操作的方法。這些方法生成一個新的表對象,表示在輸入表的基礎上計算完成後的結果。一些計算由多個方法組成,如,table.groupBy(...).select()。

更多關於 Table API 的使用請查閱://TODO


 
  1. // get a TableEnvironment

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

  3.  
  4. // register Orders table

  5.  
  6. // scan registered Orders table

  7. val orders = tableEnv.scan("Orders")

  8. // compute revenue for all customers from France

  9. val revenue = orders

  10. .filter('cCountry === "FRANCE")

  11. .groupBy('cID, 'cName)

  12. .select('cID, 'cName, 'revenue.sum AS 'revSum)

  13.  
  14. // emit or convert Table

  15. // execute query

注意:Scala Table API 使用一個單引號(')來表示引用表。Table API使用的是 Scala 隱式,必須導入org.apache.flink.api.scala._ 和 org.apache.flink.table.api.scala._

SQL

Flink SQL 的集成基於 Apache Calcite,Calcite 實現了 SQL 標準,SQL 查詢被指定爲sql字符串。

下面的例子展示瞭如何指定一個查詢,並返回結果數據表:


 
  1. // get a TableEnvironment

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

  3.  
  4. // register Orders table

  5.  
  6. // compute revenue for all customers from France

  7. val revenue = tableEnv.sqlQuery("""

  8. |SELECT cID, cName, SUM(revenue) AS revSum

  9. |FROM Orders

  10. |WHERE cCountry = 'FRANCE'

  11. |GROUP BY cID, cName

  12. """.stripMargin)

  13.  
  14. // emit or convert Table

  15. // execute query

下面的示例顯示如何指定將結果插入已註冊表的 update 查詢:


 
  1. // get a TableEnvironment

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

  3.  
  4. // register "Orders" table

  5. // register "RevenueFrance" output table

  6.  
  7. // compute revenue for all customers from France and emit to "RevenueFrance"

  8. tableEnv.sqlUpdate("""

  9. |INSERT INTO RevenueFrance

  10. |SELECT cID, cName, SUM(revenue) AS revSum

  11. |FROM Orders

  12. |WHERE cCountry = 'FRANCE'

  13. |GROUP BY cID, cName

  14. """.stripMargin)

  15.  
  16. // execute query

混合使用 Table API & SQL

因爲 Table API 和 SQL 返回的都是 Table 對象,所以他們是可以混合使用的。

  • 可以在 SQL 查詢返回的表對象上定義 Table API 查詢。
  • 通過在 TableEnvironment 中註冊結果表並在 SQL 查詢的 FROM 子句中引用它,可以根據 Table API 查詢的結果定義 SQL 查詢。

寫表

Table 是通過 TableSink 將數據寫出的。TableSink 是一個通用的接口,支持多種文件格式(CSV, Apache Parquet, Apache Avro),存儲系統(JDBC, Apache HBase, Apache Cassandra, Elasticsearch),消息系統(Apache Kafka, RabbitMQ)。

批處理表只能寫入 BatchTableSink,而流表需要 AppendStreamTableSink、RetractStreamTableSink 或 UpsertStreamTableSink。

有關可用 Sink 的詳細信息,以及如何實現自定義 TableSink 的說明,請參閱://TODO。

Table.insertInto(String tableName) 方法將一個 Table 寫入到已經註冊的 TableSink。該方法通過名稱在 catalog 中查詢 TableSink 並驗證 Table Schema 和 TableSink Schema 是否一致。


 
  1. // get a TableEnvironment

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

  3.  
  4. // create a TableSink

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

  6.  
  7. // register the TableSink with a specific schema

  8. val fieldNames: Array[String] = Array("a", "b", "c")

  9. val fieldTypes: Array[TypeInformation] = Array(Types.INT, Types.STRING, Types.LONG)

  10. tableEnv.registerTableSink("CsvSinkTable", fieldNames, fieldTypes, sink)

  11.  
  12. // compute a result Table using Table API operators and/or SQL queries

  13. val result: Table = ...

  14.  
  15. // emit the result Table to the registered TableSink

  16. result.insertInto("CsvSinkTable")

  17.  
  18. // execute the program

轉化並執行查詢

對於現有的兩個 planner,轉化和執行查詢是不相同的。

Old Planner

Table API 和 SQL 查詢根據其輸入是流輸入還是批輸入被轉換爲 DataStream 或 DataSet 程序。查詢在內部表示爲邏輯查詢計劃,並分兩個階段解釋:

  1. 優化邏輯計劃
  2. 轉換爲 DataStream 或 DataSet 程序

Table API 或 SQL 查詢在以下情況下轉化:

  • 調用 Table.insertInto()方法將 Table 寫入 TableSink
  • 調用 TableEnvironment.sqlUpdate()方法指定 SQL 更新查詢
  • Table 被轉化成 DataStream 或 DataSet(查閱:與DataStream和DataSet API集成

轉換後,Table API 或 SQL 查詢的處理方式與常規 DataStream 或 DataSet 程序類似,並在調用 StreamExecutionEnvironment.execute()或 ExecutionEnvironment.execute()時執行。

Blink Planner

Table API 和 SQL 查詢被轉換爲數據流程序或批處理程序。查詢在內部表示爲邏輯查詢計劃,並分兩個階段轉換:

  1. 優化邏輯計劃
  2. 轉換爲 DataStream 程序

對於 TableEnvironment 和 StreamTableEnvironment,轉換查詢是不同的。對於 TableEnvironment,當調用TableEnvironment.execute()時,將轉換 Table API 或 SQL 查詢,因爲 TableEnvironment 將把多個 Sink 優化爲一個DAG。

而對於 StreamTableEnvironment,當以下情況發生時,Table API 和 SQL 纔會被轉換:

  • 調用 Table.insertInto()方法將 Table 寫入 TableSink
  • 調用 TableEnvironment.sqlUpdate()方法指定 SQL 更新查詢
  • Table 轉換成 DataStream

一旦轉換完成,Table API 或 SQL 查詢就像普通的 DataStream 程序一樣處理,並在調用 TableEnvironment.execute() 或 StreamExecutionEnvironment.execute() 時執行。

與DataStream和DataSet API集成

流上的兩個 planner 都可以與 DataStream API 集成。只有老的 planner 才能與 DataSet API 集成,批量上的 Blink planner 不能同時與兩者結合。注意:下面討論的 DataSet API 只適用於舊的批處理 planner。

Table API 和 SQL 查詢可以很容易地與 DataStream 和 DataSet 程序集成並嵌入其中。例如,可以查詢外部表(例如,RDBMS),做一些預處理,如 filtering, projecting, aggregating, joining 元數據,然後進一步處理 DataStream 數據或 DataSet 的API(任何構建在這些api之上的庫,如 CEP 或 Gelly)。相反,Table API 或 SQL 查詢也可以應用在 DataStream 或 DataSet 程序的結果上。

這種交互可以通過將 DataStream 或 DataSet 轉換爲表來實現,反之亦然。

Scala的隱式轉換

Scala Table API 爲 DataSet、DataStream 和 Table 類提供隱式轉換。通過導入org.apache.flink.table.api.scala包,可以啓用這些轉換。除了org.apache.flink.api.scala。_用於Scala DataStream API。

將DataStream和DataSet註冊爲表

DataStream 或 DataSet 可以在 TableEnvironment 中註冊爲表。結果表的模式取決於已註冊的 DataStream 或 DataSet 的數據類型。有關數據類型到表模式的映射的詳細信息,請參閱:數據類型到表模式的映射


 
  1. // get TableEnvironment

  2. // registration of a DataSet is equivalent

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

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

  6.  
  7. // register the DataStream as Table "myTable" with fields "f0", "f1"

  8. tableEnv.registerDataStream("myTable", stream)

  9.  
  10. // register the DataStream as table "myTable2" with fields "myLong", "myString"

  11. tableEnv.registerDataStream("myTable2", stream, 'myLong, 'myString)

注意:DataStream Table 名稱不能與 ^_DataStreamTable_[0-9]+ 相匹配,DataSet Table 名稱不能與 ^_DataSetTable_[0-9]+ 相匹配。因爲他們僅提供給內部使用。 

將DataStream和DataSet轉換爲表

不需要在 TableEnvironment 中註冊 DataStream 或 DataSet,它還可以直接轉換爲表。


 
  1. // get TableEnvironment

  2. // registration of a DataSet is equivalent

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

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

  6.  
  7. // convert the DataStream into a Table with default fields '_1, '_2

  8. val table1: Table = tableEnv.fromDataStream(stream)

  9.  
  10. // convert the DataStream into a Table with fields 'myLong, 'myString

  11. val table2: Table = tableEnv.fromDataStream(stream, 'myLong, 'myString)

將錶轉換爲DataStream或DataSet

表可以轉換爲 DataStream 或 DataSet。通過這種方式,可以對 Table API 或 SQL 查詢的結果運行自定義 DataStream 或 DataSet 程序。

在將錶轉換爲 DataStream 或 DataSet 時,需要指定結果 DataStream 或 DataSet 的數據類型,即,要將表的行轉換成的數據類型。通常最方便的轉換類型是 Row。以下是不同選項的功能概覽:

  • Row:字段由位置、任意數量的字段、對空值的支持、沒有類型安全訪問來映射。
  • POJO:字段按名稱映射(POJO字段必須命名爲表字段)、任意數量的字段、支持空值、類型安全訪問。
  • Case Class:字段按位置映射,不支持空值,類型安全訪問。
  • Tuple:字段按位置映射,限制爲22 (Scala)或25 (Java)字段,不支持空值,類型安全訪問。
  • Atomic Type:表必須有一個字段,不支持空值,類型安全訪問。

將錶轉換爲DataStream

流查詢的結果表將被動態更新,即,隨着新記錄到達查詢的輸入流,它也在發生變化。因此,將這樣的動態查詢轉換成的 DataStream 需要對錶的更新進行編碼。

將錶轉換爲 DataStream 有兩種模式:

  1. 追加:只有動態表被 insert 修改時,該模式纔可用。它只追加數據,之前寫入的數據不會被修改。
  2. 撤回:這種模式總是可以使用。它使用 boolean 標誌對插入和刪除更改進行編碼。

 
  1. // get TableEnvironment.

  2. // registration of a DataSet is equivalent

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

  4.  
  5. // Table with two fields (String name, Integer age)

  6. val table: Table = ...

  7.  
  8. // convert the Table into an append DataStream of Row

  9. val dsRow: DataStream[Row] = tableEnv.toAppendStream[Row](table)

  10.  
  11. // convert the Table into an append DataStream of Tuple2[String, Int]

  12. val dsTuple: DataStream[(String, Int)] dsTuple =

  13. tableEnv.toAppendStream[(String, Int)](table)

  14.  
  15. // convert the Table into a retract DataStream of Row.

  16. // A retract stream of type X is a DataStream[(Boolean, X)].

  17. // The boolean field indicates the type of the change.

  18. // True is INSERT, false is DELETE.

  19. val retractStream: DataStream[(Boolean, Row)] = tableEnv.toRetractStream[Row](table)

注意:關於動態表及其屬性的詳細討論在動態表文檔中給出(//TODO)。 

將錶轉換爲DataSet

將錶轉換爲 DataSet 的過程如下:


 
  1. // get TableEnvironment

  2. // registration of a DataSet is equivalent

  3. val tableEnv = BatchTableEnvironment.create(env)

  4.  
  5. // Table with two fields (String name, Integer age)

  6. val table: Table = ...

  7.  
  8. // convert the Table into a DataSet of Row

  9. val dsRow: DataSet[Row] = tableEnv.toDataSet[Row](table)

  10.  
  11. // convert the Table into a DataSet of Tuple2[String, Int]

  12. val dsTuple: DataSet[(String, Int)] = tableEnv.toDataSet[(String, Int)](table)

數據類型到表模式的映射

Flink 的 DataStream 和 DataSet APIs 支持不同的類型。複合類型如元組(內置Scala和Flink Java元組)、pojo、Scala case類和Flink 的 Row 類型,允許嵌套數據結構,其中包含多個字段,可以在 table 表達式中訪問。其他類型被視爲原子類型。在下面的文章中,我們將描述 Table API 如何將這些類型轉換爲內部行表示,並展示將 DataStream 轉換爲表的示例。

數據類型到表模式的映射可以通過兩種方式進行:基於字段位置或基於字段名稱

基於位置映射

基於位置的映射可用於在保持字段順序的同時賦予字段更有意義的名稱。此映射可用於具有已定義字段順序的組合數據類型和原子類型。元組、行和case類等組合數據類型具有這樣的字段順序。但是,必須根據字段名映射POJO的字段(請參閱下一節)。字段可以被投影出來,但不能使用別名重命名。

在定義基於位置的映射時,輸入數據類型中必須不存在指定的名稱,否則API將假定映射應該基於字段名稱進行。如果沒有指定字段名,則使用組合類型的默認字段名和字段順序,或者使用f0表示原子類型。


 
  1. // get a TableEnvironment

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

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

  5.  
  6. // convert DataStream into Table with default field names "_1" and "_2"

  7. val table: Table = tableEnv.fromDataStream(stream)

  8.  
  9. // convert DataStream into Table with field "myLong" only

  10. val table: Table = tableEnv.fromDataStream(stream, 'myLong)

  11.  
  12. // convert DataStream into Table with field names "myLong" and "myInt"

  13. val table: Table = tableEnv.fromDataStream(stream, 'myLong, 'myInt)

基於名稱映射

基於名稱的映射可以用於任何數據類型,包括pojo。這是定義表模式映射最靈活的方法。映射中的所有字段都按名稱引用,可以使用別名 as 重命名。字段可以重新排序並投影出來。

如果沒有指定字段名,則使用組合類型的默認字段名和字段順序,或者使用f0表示原子類型。


 
  1. // get a TableEnvironment

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

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

  5.  
  6. // convert DataStream into Table with default field names "_1" and "_2"

  7. val table: Table = tableEnv.fromDataStream(stream)

  8.  
  9. // convert DataStream into Table with field "_2" only

  10. val table: Table = tableEnv.fromDataStream(stream, '_2)

  11.  
  12. // convert DataStream into Table with swapped fields

  13. val table: Table = tableEnv.fromDataStream(stream, '_2, '_1)

  14.  
  15. // convert DataStream into Table with swapped fields and field names "myInt" and "myLong"

  16. val table: Table = tableEnv.fromDataStream(stream, '_2 as 'myInt, '_1 as 'myLong)

原子類型

Flink 將基本類型(IntegerDoubleString)或泛型類型(不能分析和分解的類型)視爲原子類型。原子類型的 DataStream 或 DataStream 被轉換爲具有單個屬性的表。屬性的類型從原子類型推斷,可以指定屬性的名稱。


 
  1. // get a TableEnvironment

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

  3.  
  4. val stream: DataStream[Long] = ...

  5.  
  6. // convert DataStream into Table with default field name "f0"

  7. val table: Table = tableEnv.fromDataStream(stream)

  8.  
  9. // convert DataStream into Table with field name "myLong"

  10. val table: Table = tableEnv.fromDataStream(stream, 'myLong)

元組(Scala和Java)和Case類(僅限Scala)

Flink支持Scala的內置元組,併爲Java提供了自己的元組類。這兩種元組的數據流和數據集都可以轉換爲表。可以通過爲所有字段提供名稱(基於位置的映射)來重命名字段。如果沒有指定字段名,則使用默認字段名。如果引用原始字段名(Flink元組的f0、f1、…和Scala元組的_1、_2、…),則API假定映射是基於名稱的,而不是基於位置的。基於名稱的映射允許使用別名(as)對字段和投影進行重新排序。


 
  1. // get a TableEnvironment

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

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

  5.  
  6. // convert DataStream into Table with renamed default field names '_1, '_2

  7. val table: Table = tableEnv.fromDataStream(stream)

  8.  
  9. // convert DataStream into Table with field names "myLong", "myString" (position-based)

  10. val table: Table = tableEnv.fromDataStream(stream, 'myLong, 'myString)

  11.  
  12. // convert DataStream into Table with reordered fields "_2", "_1" (name-based)

  13. val table: Table = tableEnv.fromDataStream(stream, '_2, '_1)

  14.  
  15. // convert DataStream into Table with projected field "_2" (name-based)

  16. val table: Table = tableEnv.fromDataStream(stream, '_2)

  17.  
  18. // convert DataStream into Table with reordered and aliased fields "myString", "myLong" (name-based)

  19. val table: Table = tableEnv.fromDataStream(stream, '_2 as 'myString, '_1 as 'myLong)

  20.  
  21. // define case class

  22. case class Person(name: String, age: Int)

  23. val streamCC: DataStream[Person] = ...

  24.  
  25. // convert DataStream into Table with default field names 'name, 'age

  26. val table = tableEnv.fromDataStream(streamCC)

  27.  
  28. // convert DataStream into Table with field names 'myName, 'myAge (position-based)

  29. val table = tableEnv.fromDataStream(streamCC, 'myName, 'myAge)

  30.  
  31. // convert DataStream into Table with reordered and aliased fields "myAge", "myName" (name-based)

  32. val table: Table = tableEnv.fromDataStream(stream, 'age as 'myAge, 'name as 'myName)

POJO (Java and Scala)

Flink支持將pojo作爲複合類型。請查閱:Flink基礎之API,DataSet、DataStream、批、流

在不指定字段名的情況下將POJO DataStream或DataSet轉換爲表時,將使用原始POJO字段的名稱。名稱映射需要原始名稱,不能通過位置來完成。字段可以使用別名(帶as關鍵字)重命名、重新排序和投影。


 
  1. // get a TableEnvironment

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

  3.  
  4. // Person is a POJO with field names "name" and "age"

  5. val stream: DataStream[Person] = ...

  6.  
  7. // convert DataStream into Table with default field names "age", "name" (fields are ordered by name!)

  8. val table: Table = tableEnv.fromDataStream(stream)

  9.  
  10. // convert DataStream into Table with renamed fields "myAge", "myName" (name-based)

  11. val table: Table = tableEnv.fromDataStream(stream, 'age as 'myAge, 'name as 'myName)

  12.  
  13. // convert DataStream into Table with projected field "name" (name-based)

  14. val table: Table = tableEnv.fromDataStream(stream, 'name)

  15.  
  16. // convert DataStream into Table with projected and renamed field "myName" (name-based)

  17. val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName)

Row

行數據類型支持任意數量的字段和具有空值的字段。字段名可以通過RowTypeInfo指定,也可以在將行DataStream或DataSet轉換爲表時指定。行類型支持按位置和名稱映射字段。字段可以通過爲所有字段提供名稱(基於位置的映射)來重命名,或者單獨選擇投影/排序/重命名(基於名稱的映射)。


 
  1. // get a TableEnvironment

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

  3.  
  4. // DataStream of Row with two fields "name" and "age" specified in `RowTypeInfo`

  5. val stream: DataStream[Row] = ...

  6.  
  7. // convert DataStream into Table with default field names "name", "age"

  8. val table: Table = tableEnv.fromDataStream(stream)

  9.  
  10. // convert DataStream into Table with renamed field names "myName", "myAge" (position-based)

  11. val table: Table = tableEnv.fromDataStream(stream, 'myName, 'myAge)

  12.  
  13. // convert DataStream into Table with renamed fields "myName", "myAge" (name-based)

  14. val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName, 'age as 'myAge)

  15.  
  16. // convert DataStream into Table with projected field "name" (name-based)

  17. val table: Table = tableEnv.fromDataStream(stream, 'name)

  18.  
  19. // convert DataStream into Table with projected and renamed field "myName" (name-based)

  20. val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName)

優化查詢

Old planner

Apache Flink 利用Apache Calcite 優化和解釋查詢。當前執行的優化包括 projection and filter push-down, subquery decorrelation, and other kinds of query rewriting。Old planner還沒有優化 join 順序,但是會按照查詢中定義的順序執行它們(FROM子句中的表順序和/或WHERE子句中的連接謂詞順序)。

通過提供一個 CalciteConfig 對象,可以調整應用於不同階段的優化規則集。這可以通過調用 calciteConfig . createbuilder()通過構建器創建,並通過調用 tableEnv.getConfig.setPlannerConfig(calciteConfig)提供給 TableEnvironment。

Blink Planner

Apache Flink 利用並擴展了 Apache Calcite 來執行復雜的查詢優化。這包括一系列基於規則和成本的優化,如:

基於Apache Calcite 的子查詢

  • 項目裁剪
  • 分區裁剪
  • 過濾疊加
  • 分計劃重複數據刪除,以避免重複計算
  • 特殊的子查詢重寫,包括兩部分:
  1. 將其轉換爲左半連接
  2. 將NOT IN和NOT EXISTS轉換爲左反連接
  • 可選項 join 重排序
  1. 啓用table.optimizer.join-reorder-enabled

注意:IN/EXISTS/NOT IN/NOT EXISTS目前只支持子查詢重寫中的連接條件。

優化器不僅基於計劃,還基於數據源提供的豐富統計信息和每個算子(如io、cpu、網絡和內存)的細粒度成本做出智能決策。 

高級用戶可以通過 CalciteConfig 對象提供自定義優化,該對象可以通過調用 TableEnvironment#getConfig#setPlannerConfig 提供給表環境。

解釋表

Table API 提供了一種機制來解釋計算表的邏輯和優化查詢計劃。這是通過 TableEnvironment.explain(table)方法或 TableEnvironment.explain()方法完成的。explain(table)返回給定表的計劃。Table.explain()返回多 sink 計劃的結果,主要用於Blink planner。它返回一個字符串,描述三個計劃:

  1. 關係查詢的抽象語法樹,即,未經優化的邏輯查詢計劃
  2. 優化的邏輯查詢計劃
  3. 物理執行計劃

下面的代碼顯示了一個示例和使用explain(Table)給出的表的相應輸出:


 
  1. val env = StreamExecutionEnvironment.getExecutionEnvironment

  2. val tEnv = StreamTableEnvironment.create(env)

  3.  
  4. val table1 = env.fromElements((1, "hello")).toTable(tEnv, 'count, 'word)

  5. val table2 = env.fromElements((1, "hello")).toTable(tEnv, 'count, 'word)

  6. val table = table1

  7. .where('word.like("F%"))

  8. .unionAll(table2)

  9.  
  10. val explanation: String = tEnv.explain(table)

  11. println(explanation)


 
  1. == Abstract Syntax Tree ==

  2. LogicalUnion(all=[true])

  3. LogicalFilter(condition=[LIKE($1, _UTF-16LE'F%')])

  4. FlinkLogicalDataStreamScan(id=[1], fields=[count, word])

  5. FlinkLogicalDataStreamScan(id=[2], fields=[count, word])

  6.  
  7. == Optimized Logical Plan ==

  8. DataStreamUnion(all=[true], union all=[count, word])

  9. DataStreamCalc(select=[count, word], where=[LIKE(word, _UTF-16LE'F%')])

  10. DataStreamScan(id=[1], fields=[count, word])

  11. DataStreamScan(id=[2], fields=[count, word])

  12.  
  13. == Physical Execution Plan ==

  14. Stage 1 : Data Source

  15. content : collect elements with CollectionInputFormat

  16.  
  17. Stage 2 : Data Source

  18. content : collect elements with CollectionInputFormat

  19.  
  20. Stage 3 : Operator

  21. content : from: (count, word)

  22. ship_strategy : REBALANCE

  23.  
  24. Stage 4 : Operator

  25. content : where: (LIKE(word, _UTF-16LE'F%')), select: (count, word)

  26. ship_strategy : FORWARD

  27.  
  28. Stage 5 : Operator

  29. content : from: (count, word)

  30. ship_strategy : REBALANCE


 
  1. val settings = EnvironmentSettings.newInstance.useBlinkPlanner.inStreamingMode.build

  2. val tEnv = TableEnvironment.create(settings)

  3.  
  4. val fieldNames = Array("count", "word")

  5. val fieldTypes = Array[TypeInformation[_]](Types.INT, Types.STRING)

  6. tEnv.registerTableSource("MySource1", new CsvTableSource("/source/path1", fieldNames, fieldTypes))

  7. tEnv.registerTableSource("MySource2", new CsvTableSource("/source/path2",fieldNames, fieldTypes))

  8. tEnv.registerTableSink("MySink1", new CsvTableSink("/sink/path1").configure(fieldNames, fieldTypes))

  9. tEnv.registerTableSink("MySink2", new CsvTableSink("/sink/path2").configure(fieldNames, fieldTypes))

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

  12. table1.insertInto("MySink1")

  13.  
  14. val table2 = table1.unionAll(tEnv.scan("MySource2"))

  15. table2.insertInto("MySink2")

  16.  
  17. val explanation = tEnv.explain(false)

  18. println(explanation)

 多 sink 計劃的結果是:


 
  1. == Abstract Syntax Tree ==

  2. LogicalSink(name=[MySink1], fields=[count, word])

  3. +- LogicalFilter(condition=[LIKE($1, _UTF-16LE'F%')])

  4. +- LogicalTableScan(table=[[default_catalog, default_database, MySource1, source: [CsvTableSource(read fields: count, word)]]])

  5.  
  6. LogicalSink(name=[MySink2], fields=[count, word])

  7. +- LogicalUnion(all=[true])

  8. :- LogicalFilter(condition=[LIKE($1, _UTF-16LE'F%')])

  9. : +- LogicalTableScan(table=[[default_catalog, default_database, MySource1, source: [CsvTableSource(read fields: count, word)]]])

  10. +- LogicalTableScan(table=[[default_catalog, default_database, MySource2, source: [CsvTableSource(read fields: count, word)]]])

  11.  
  12. == Optimized Logical Plan ==

  13. Calc(select=[count, word], where=[LIKE(word, _UTF-16LE'F%')], reuse_id=[1])

  14. +- TableSourceScan(table=[[default_catalog, default_database, MySource1, source: [CsvTableSource(read fields: count, word)]]], fields=[count, word])

  15.  
  16. Sink(name=[MySink1], fields=[count, word])

  17. +- Reused(reference_id=[1])

  18.  
  19. Sink(name=[MySink2], fields=[count, word])

  20. +- Union(all=[true], union=[count, word])

  21. :- Reused(reference_id=[1])

  22. +- TableSourceScan(table=[[default_catalog, default_database, MySource2, source: [CsvTableSource(read fields: count, word)]]], fields=[count, word])

  23.  
  24. == Physical Execution Plan ==

  25. Stage 1 : Data Source

  26. content : collect elements with CollectionInputFormat

  27.  
  28. Stage 2 : Operator

  29. content : CsvTableSource(read fields: count, word)

  30. ship_strategy : REBALANCE

  31.  
  32. Stage 3 : Operator

  33. content : SourceConversion(table:Buffer(default_catalog, default_database, MySource1, source: [CsvTableSource(read fields: count, word)]), fields:(count, word))

  34. ship_strategy : FORWARD

  35.  
  36. Stage 4 : Operator

  37. content : Calc(where: (word LIKE _UTF-16LE'F%'), select: (count, word))

  38. ship_strategy : FORWARD

  39.  
  40. Stage 5 : Operator

  41. content : SinkConversionToRow

  42. ship_strategy : FORWARD

  43.  
  44. Stage 6 : Operator

  45. content : Map

  46. ship_strategy : FORWARD

  47.  
  48. Stage 8 : Data Source

  49. content : collect elements with CollectionInputFormat

  50.  
  51. Stage 9 : Operator

  52. content : CsvTableSource(read fields: count, word)

  53. ship_strategy : REBALANCE

  54.  
  55. Stage 10 : Operator

  56. content : SourceConversion(table:Buffer(default_catalog, default_database, MySource2, source: [CsvTableSource(read fields: count, word)]), fields:(count, word))

  57. ship_strategy : FORWARD

  58.  
  59. Stage 12 : Operator

  60. content : SinkConversionToRow

  61. ship_strategy : FORWARD

  62.  
  63. Stage 13 : Operator

  64. content : Map

  65. ship_strategy : FORWARD

  66.  
  67. Stage 7 : Data Sink

  68. content : Sink: CsvTableSink(count, word)

  69. ship_strategy : FORWARD

  70.  
  71. Stage 14 : Data Sink

  72. content : Sink: CsvTableSink(count, word)

  73. ship_strategy : FORWARD

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章