Table API & SQL——概念和通用 API(1.14.4)

Table API和SQL接口被整合成一个联合API,其主要概念是围绕Table对象进行输入和输出查询操作。另外,由于其和DataStream API 很容易被整合在一起,所以,在开发过程中是可以随意相互转换操作的。

1. 所需依赖

Table API & SQL接口和DataStream API无缝衔接,他们之间可以很容易的相互转换。使用这些接口构建程序需要如下依赖:

<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-table-api-scala-bridge_2.11</artifactId>
  <version>1.14.4</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-table-planner_2.11</artifactId>
  <version>1.14.4</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-streaming-scala_2.11</artifactId>
  <version>1.14.4</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-table-common</artifactId>
  <version>1.14.4</version>
  <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-clients_2.12</artifactId>
    <version>1.14.4</version>
</dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table-planner_2.11</artifactId>
    <version>1.14.4</version>
    <scope>provided</scope>
</dependency>

2. Table API and SQL 程序结构

常见的Table API and SQL程序结构如下所示。

import org.apache.flink.table.api._
import org.apache.flink.connector.datagen.table.DataGenOptions

// Create a TableEnvironment for batch or streaming execution.
// See the "Create a TableEnvironment" section for details.
val tableEnv = TableEnvironment.create(/*…*/)

// Create a source table 创建数据源表,这里是连接数据源创建了一个临时表SourceTable
tableEnv.createTemporaryTable("SourceTable", TableDescriptor.forConnector("datagen")
  .schema(Schema.newBuilder() // 定义表Schema
    .column("f0", DataTypes.STRING()) // 只有一个字段,字段名为f0
    .build())
  .option(DataGenOptions.ROWS_PER_SECOND, 100)
  .build())

// Create a sink table (using SQL DDL) 用标准sql创建临时的sink表
tableEnv.executeSql("CREATE TEMPORARY TABLE SinkTable WITH ('connector' = 'blackhole') LIKE SourceTable");

// Create a Table object from a Table API query 创建表对象
val table1 = tableEnv.from("SourceTable");

// Create a Table object from a SQL query 用标准sql的方式创建表对象
val table2 = tableEnv.sqlQuery("SELECT * FROM SourceTable");

// Emit a Table API result Table to a TableSink, same for SQL result 把数据输入sink表
val tableResult = table1.executeInsert("SinkTable");

3. 创建表环境

TableEnvironment是Table API和SQL程序的入口,主要负责:

在内部目录中创建表
创建目录
加载可插拔模块
执行sql查询操作
注册用户自定义函数
DataStream和Table之间的转换

Table对象总是绑定到特定的TableEnvironment,TableEnvironment对象是由TableEnvironment.create()静态方法创建的,所以不可能对来自不同TableEnvironment的表做相关query操作,例如,join,union等。

import org.apache.flink.table.api.{EnvironmentSettings, TableEnvironment}

val settings = EnvironmentSettings
    .newInstance()
    .inStreamingMode()
    //.inBatchMode()
    .build()

val tEnv = TableEnvironment.create(settings)

从已经创建的StreamExecutionEnvironment 创建StreamTableEnvironment对象:

import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.table.api.EnvironmentSettings
import org.apache.flink.table.api.bridge.scala.StreamTableEnvironment

val env = StreamExecutionEnvironment.getExecutionEnvironment
val tEnv = StreamTableEnvironment.create(env)

4. 在表目录中创建表

TableEnvironment对象维护一个用标识符创建的表目录对象,每个标识符由3部分组成:目录名,数据库名和表名。如果目录或数据库名未指定,将会默认未当前值。
表可以是虚拟的(views)或真是存在的物理表。views可以从已经存在的Table对象创建,通常是Table API 或SQL查询结果。物理表可以是外部数据,例如,文件、数据库表或消息队列。

4.1 临时表和永久表

临时表和单个Flink会话生命周期相关,永久表对所有集群的Flink会话可见。

永久表需要维护其元数据目录,例如,hive元数据。

临时表被存储在内存中,其生命周期也绑定在创建它的Flink会话上,这些表不能被其他的Flink会话访问。他们不依赖于任何一个目录或数据库,但是可以被其中任何一个命名空间创建。临时表不会因为其中的一个数据库被删除而被删除。

4.1.1 临时表遮蔽永久表

可能在创建临时表时,临时表的标识符和永久表的一样,临时表会掩盖永久表,使永久表在临时表存在期间不能被访问。所有的查询都会对临时表进行执行。这可能对测试程序很有用,根据真正数据集创建一个子集临时表,或者其数据是测试数据。一旦验证正确再执行真正的生产表。

4.2 创建Table

4.2.1 虚拟表

在SQL术语中,Table API对象是一个视图(view,虚拟表),它封装了一个逻辑查询计划,可以在一个目录中被创建:

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

// table is the result of a simple projection query  
val projTable: Table = tableEnv.from("X").select(...) // X是一个已经创建的视图名(虚拟表)

// register the Table projTable as table "projectedTable"
tableEnv.createTemporaryView("projectedTable", projTable) // 根据表对象projTable,创建临时表projectedTable

表对象和关系型数据库中创建的view相似,这个表对象是没有被优化的,但是当其他查询也引用到这个表对象时,会被内联在一起的。如果多个查询都引用同一个注册的表对象,它将分别内联到查询,而且会执行多次,也就是说表对象是不被共享的。

4.2.2 连接器表

利用连接器声明可以在关系型数据库的基础上建立表对象。连接器描述了存储表数据的外部系统,如:kafka或标准的文件系统。

表对象可以用Table API或转换成SQL DDL创建。

// Using table descriptors
final TableDescriptor sourceDescriptor = TableDescriptor.forConnector("datagen")
    .schema(Schema.newBuilder()
    .column("f0", DataTypes.STRING())
    .build())
    .option(DataGenOptions.ROWS_PER_SECOND, 100)
    .build();

tableEnv.createTable("SourceTableA", sourceDescriptor);
tableEnv.createTemporaryTable("SourceTableB", sourceDescriptor);

// Using SQL DDL
tableEnv.executeSql("CREATE [TEMPORARY] TABLE MyTable (...) WITH (...)")

4.3 表标识符扩展

表对象总是标识符注册,标识符由三部分组成,目录、数据库名和表名。

用户可以将一个目录和其中的一个数据库设置为“当前目录”和“当前数据库”。在当前目录、数据库下,标识符的前两部分是可选的,这样就会默认未当前目录和数据库。用户可以自己转换当前目录和当前数据库。

标识符符合SQL要求,这意味着它们可以用反引号(')进行转义。

// get a TableEnvironment
val tEnv: TableEnvironment = ...;
tEnv.useCatalog("custom_catalog") // 设定使用的目录
tEnv.useDatabase("custom_database") // 设定使用的数据库

val table: Table = ...;

// register the view named 'exampleView' in the catalog named 'custom_catalog'
// in the database named 'custom_database' 
tableEnv.createTemporaryView("exampleView", table) // 未指定目录和数据库,则默认为当前目录和数据库

// register the view named 'exampleView' in the catalog named 'custom_catalog'
// in the database named 'other_database' 
tableEnv.createTemporaryView("other_database.exampleView", table) // 只指定了数据库,则目录默认为当前目录

// register the view named 'example.View' in the catalog named 'custom_catalog'
// in the database named 'custom_database' 
tableEnv.createTemporaryView("`example.View`", table) // 反引号表明其是一体的,只代表表名,目录及数据库名默认为当前值

// register the view named 'exampleView' in the catalog named 'other_catalog'
// in the database named 'other_database' 
tableEnv.createTemporaryView("other_catalog.other_database.exampleView", table) // 直接指定目录和数据库

5. Query a Table

5.1 Table API

Table API是一个用于Scala和Java的语言集成查询API。与SQL相反,查询不是指定为string,而是在宿主语言中逐步组合的。

这个API基于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.from("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 String插值来引用Table的属性。Table API使用Scala隐式,需要导入下面这些隐式转换:

org.apache.flink.table.api._ - for implicit expression conversions
org.apache.flink.api.scala._ and org.apache.flink.table.api.bridge.scala._ if you want to convert from/to DataStream.

5.2 SQL

Flink的 SQL 集成基于 Apache Calcite ,后者实现了SQL标准。SQL查询被指定为常规字符串。

SQL 文档描述了Flink对流表和批处理表的SQL支持。

下面的示例显示如何指定查询并将结果作为表返回。

// 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.executeSql("""
  |INSERT INTO RevenueFrance
  |SELECT cID, cName, SUM(revenue) AS revSum
  |FROM Orders
  |WHERE cCountry = 'FRANCE'
  |GROUP BY cID, cName
  """.stripMargin)

5.3 Table API 和 SQL混用

Table API & SQL查询可以很容易混用,因为他们都返回表对象:

Table API 查询可以基于一个由SQL查询返回的表对象
可以在Table API查询的结果上定义SQL查询,方法是将结果表注册到TableEnvironment中,并在SQL查询的FROM子句中引用它。

6. 输出表对象

可以通过TableSink将表对象写入目标表。TableSink 是一个通用接口,支持各种文件格式(例如,CSV,Apache Parquet, Apache Avro),存储系统(例如,JDBC,Apache HBase, Apache Cassandra,Elasticsearch), 或消息系统(例如,Apache Kafka, RabbitMQ)。

批数据表仅能写入BatchTableSink,而流数据表需要AppendStreamTableSink,RetractStreamTableSink或UpsertStreamTableSink。

Table.executeInsert(String tableName) 方法将表对象数据写入已经注册的TableSink。该方法通过名称从目录中查询TableSink,确认表对象的Schema是否和TableSink的一致。

下面是一个简单的示例:

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

// create an output Table
val schema = Schema.newBuilder()
    .column("a", DataTypes.INT())
    .column("b", DataTypes.STRING())
    .column("c", DataTypes.BIGINT())
    .build()

tableEnv.createTemporaryTable("CsvSinkTable", TableDescriptor.forConnector("filesystem")
    .schema(schema)
    .option("path", "/path/to/file")
    .format(FormatDescriptor.forFormat("csv")
        .option("field-delimiter", "|")
        .build())
    .build())

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

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

7. 转换和执行Query

表API和SQL查询被转换成DataStream程序,无论它们的输入是流的还是批处理的。查询在内部被表示为逻辑查询计划,并被转换为两个阶段:

优化逻辑计划,
转换为DataStream程序。

这些方法被调用时,Table API & SQL 查询被转化为DataStream程序:

TableEnvironment.executeSql()
Table.executeInsert()
Table.execute()
StatementSet.execute()
Table对象被转换成DataStream,一旦被转换,它就是一个标准DataStream程序,当 StreamExecutionEnvironment.execute() 方法调用时被执行

8. Query优化

Apache Flink利用和扩展Apache Calcite来执行复杂的查询优化。这包括一系列基于规则和成本的优化,例如:

Subquery decorrelation based on Apache Calcite
Project pruning
Partition pruning
Filter push-down
Sub-plan deduplication to avoid duplicate computation
Special subquery rewriting, including two parts:
    Converts IN and EXISTS into left semi-joins
    Converts NOT IN and NOT EXISTS into left anti-join
Optional join reordering
    Enabled via table.optimizer.join-reorder-enabled

IN/EXISTS/NOT IN/NOT EXISTS目前只支持在子查询重写的连接条件下使用。

优化器不仅根据计划,还根据来自数据源的丰富统计信息和每个操作符的细粒度成本(如io、cpu、网络和内存)做出智能决策。

高级用户可以通过CalciteConfig对象提供自定义优化,该对象可以通过调用TableEnvironment#getConfig#setPlannerConfig提供给表环境。

9. Explaining a Table

Table API提供了一种机制来解释用于计算Table的逻辑和优化的查询计划。这是通过Table.explain()方法或StatementSet.explain()方法完成的。explain()返回一个Table的计划。statement .explain()返回多个接收器的计划。它返回一个描述三个计划的String:

关系查询的抽象语法树,即未经优化的逻辑查询计划,
优化的逻辑查询计划和
物理执行计划。

TableEnvironment.explainSql()和TableEnvironment.executeSql()支持执行一个EXPLAIN

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值