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

第一章 整体介绍 2 1.1 什么是 Table API Flink SQL 2 1.2 需要引入的依赖 2 1.3 两种 planner(old &amp; blink)的区别 4 第二章 API 调用 5 2.1 基本程序结构 5 2.2 创建表环境 5 2.3 在 Catalog 中注册表 7 2.3.1 表(Table)的概念 7 2.3.2 连接到文件系统(Csv 格式) 7 2.3.3 连接到 Kafka 8 2.4 表的查询 9 2.4.1 Table API 的调用 9 2.4.2 SQL 查询 10 2.5 将 DataStream 转换成表 11 2.5.1 代码表达 11 2.5.2 数据类型与 Table schema 的对应 12 2.6. 创建临时视图(Temporary View) 12 2.7. 输出表 14 2.7.1 输出到文件 14 2.7.2 更新模式(Update Mode) 15 2.7.3 输出到 Kafka 16 2.7.4 输出到 ElasticSearch 16 2.7.5 输出到 MySql 17 2.8 将表转换成 DataStream 18 2.9 Query 的解释执行 20 1. 优化查询计划 20 2. 解释成 DataStream 或者 DataSet 程序 20 第三章 流处理中的特殊概念 20 3.1 流处理关系代数(表,及 SQL)的区别 21 3.2 动态表(Dynamic Tables) 21 3.3 流式持续查询的过程 21 3.3.1 将流转换成表(Table) 22 3.3.2 持续查询(Continuous Query) 23 3.3.3 将动态表转换成流 23 3.4 时间特性 25 3.4.1 处理时间(Processing Time) 25 3.4.2 事件时间(Event Time) 27 第四章 窗口(Windows) 30 4.1 分组窗口(Group Windows) 30 4.1.1 滚动窗口 31 4.1.2 滑动窗口 32 4.1.3 会话窗口 32 4.2 Over Windows 33 1) 无界的 over window 33 2) 有界的 over window 34 4.3 SQL 中窗口的定义 34 4.3.1 Group Windows 34 4.3.2 Over Windows 35 4.4 代码练习(以分组滚动窗口为例) 36 第五章 函数(Functions) 38 5.1 系统内置函数 38 5.2 UDF 40 5.2.1 注册用户自定义函数 UDF 40 5.2.2 标量函数(Scalar Functions) 40 5.2.3 表函数(Table Functions) 42 5.2.4 聚合函数(Aggregate Functions) 45 5.2.5 表聚合函数(Table Aggregate Functions) 47
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值