Flink Table API编程整理

目录

Table API & SQL

Table API 特点

Table API 编程

怎么定义一个 Table

如何输出一个 table

如何查询一个 table

Table API 操作分类

Flink API 总共分为4层链接这里主要整理 Table API 的使用

Table API & SQL

TableAPIWordCount案例

tab.groupBy("word").select("word,count(1) as count")

SQLWordCount案例

SELECT word,COUNT(*) AS cnt FROM MyTable GROUP BY word

【1】声明式:用户只关系做什么,不用关心怎么做;
【2】高性能:支持查询优化,可以获取更好的执行性能,因为它的底层有一个优化器,跟SQL底层有优化器是一样的。
【3】流批统一:相同的统计逻辑,即可以流模型运行,也可以批模式运行;
【4】标准稳定:语义遵循SQL标准,不易改动。当升级等底层修改,不用考虑API兼容问题;
【5】易理解:语义明确,所见即所得;

Table API 特点

Table API 使得多声明的数据处理写起来比较容易。

#例如,我们将a<10的数据过滤插入到xxx表中
table.filter(a<10).insertInto("xxx")
#我们将a>10的数据过滤插入到yyy表中
table.filter(a>10).insertInto("yyy")

Talbe 是Flink自身的一种API 使得更容易扩展标准的 SQL(当且仅当需要的时候),两者的关系如下:

Table API 编程

WordCount 编程示例

package org.apache.flink.table.api.example.stream;

import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.TableEnvironment;
import org.apache.flink.table.api.java.StreamTableEnvironment;
import org.apache.flink.table.descriptors.FileSystem;
import org.apache.flink.table.descriptors.OldCsv;
import org.apache.flink.table.descriptors.Schema;
import org.apache.flink.types.Row;

public class JavaStreamWordCount {

	public static void main(String[] args) throws Exception {
                //获取执行环境
		StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
		StreamTableEnvironment tEnv = TableEnvironment.getTableEnvironment(env);
                //指定一个路径
		String path = JavaStreamWordCount.class.getClassLoader().getResource("words.txt").getPath();
                //指定文件格式和分隔符,对应的Schema(架构)这里只有一列,类型是String
		tEnv.connect(new FileSystem().path(path))
			.withFormat(new OldCsv().field("word", Types.STRING).lineDelimiter("\n"))
			.withSchema(new Schema().field("word", Types.STRING))
			.inAppendMode()
			.registerTableSource("fileSource");//将source注册到env中
                //通过 scan 拿到table,然后执行table的操作。
		Table result = tEnv.scan("fileSource")
			.groupBy("word")
			.select("word, count(1) as count");
                //将table输出
		tEnv.toRetractStream(result, Row.class).print();
                //执行
		env.execute();
	}
}

怎么定义一个 Table

Table myTable = tableEnvironment.scan("myTable") 都是从 Environment中 scan出来的。而这个 myTable 又是我们注册进去的。问题就是有哪些方式可以注册 Table。
【1】Table descriptor
类似于上述的WordCount,指定一个文件系统fs,也可以是kafka等,还需要一些格式和Schema等。

tEnv.connect(new FileSystem().path(path))
			.withFormat(new OldCsv().field("word", Types.STRING).lineDelimiter("\n"))
			.withSchema(new Schema().field("word", Types.STRING))
			.inAppendMode()
			.registerTableSource("fileSource");//将source注册到env中

2自定义一个 Table source然后把自己的 Table source 注册进去。

TableSource csvSource = new CsvTableSource(path,new String[]{"word"},new TypeInformation[]{Types.STRING});
tEnv.registerTableSource("sourceTable2", csvSource);

3注册一个 DataStream,例如下面一个 String类型的DataStream,命名为 myTable3 对应的 schema 只有一列叫 word。

DataStream<String> stream = ...
// register the DataStream as table " myTable3" with 
// fields "word"
tableEnv.registerDataStream("myTable3", stream, "word");

如何输出一个 table

当我们获取到一个结构表的时候(table类型)执行 insertInto 目标表中:resultTable.insertInto("TargetTable");

【1】Table descriptor:类似于注入,最终使用Sink进行输出,例如如下输出到 targetTable中,主要是最后一段的区别。

tEnv
.connect(new FileSystem().path(path)).withFormat(new OldCsv().field("word", Types.STRING)
.lineDelimiter("\n")).withSchema(new Schema()
.field("word", Types.STRING))
.registerTableSink("targetTable");

2自定义一个 Table sink输出到自己的 sinkTable2注册进去。

TableSink csvSink = new CsvTableSink(path,new String[]{"word"},new TypeInformation[]{Types.STRING});
tEnv.registerTableSink("sinkTable2", csvSink);

3输出一个 DataStream,例如下面产生一个RetractStream,对应要给 Tuple2的联系。Boolean 这行记录时add还是delete。

// emit the result table to a DataStream
DataStream<Tuple2<Boolean, Row>> stream = tableEnv.toRetractStream(resultTable, Row.class)

如何查询一个 table

为了会有 GroupedTable等,为了增加限制,写出正确的API。

Table API 操作分类

1、与 sql 对齐的操作,select、as、filter等;
2、提升 Table API 易用性的操作;
  —— Columns Operation 易用性:假设有一张 100列的表,我们需要去掉一列,需要怎么操作?第三个API可以帮你完成。我们先获取表中的所有 Column,然后通过 dropColumn去掉不需要的列即可。主要是一个Table上的算子。

Operators Examples
AddColumns Table orders = tableEnv.scan("Orders");
Table result = orders.addColumns("concat(c,'sunny')as desc"); 添加新的列,要求是列名不能重复。
addOrReplaceColumns Table orders = tableEnv.scan("Orders");
Table result = order.addOrReplaceColumns("concat(c,'sunny') as desc");添加列,如果存在则覆盖
DropColumns

Table orders = tableEnv.scan("Orders");

Table result = orders.dropColumns("b c");

RenameColumns

Table orders = tableEnv.scan("Orders");

able result = orders.RenameColumns("b as b2,c as c2);列重命名

  ——Columns Function 易用性:假设有一张表,我么需要获取第20-80列,该如何获取。类似一个函数,可以用在列选择的任何地方,例如:Table.select(withColumns(a,1 to 10))、GroupBy等等。

语法 描述
withColumns(...) 选择你指定的列
withoutColumns(...) 反选你指定的列

 
列的操作语法(建议):如下,它们都是上层包含下层的关系。

columnOperation:
    withColumns(columnExprs) / withoutColumns(columnExprs) #可以接收多个参数 columnExpr
columnExprs:
    columnExpr [, columnExpr]*  #可以分为如下三种情况
columnExpr:
    columnRef | columnIndex to columnIndex | columnName to columnName #1 cloumn引用  2下标范围操作  3名字的范围操作
columnRef:
    columnName(The field name that exists in the table) | columnIndex(a positive integer starting at 1)

Example: withColumns(a, b, 2 to 10, w to z)

Row based operation
  —— Map operation 易用性:

//方法签名: 接收一个 scalarFunction 参数,返回一个 Table
def map(scalarFunction: Expression): Table

class MyMap extends ScalarFunction {
	var param : String = ""
	
	//eval 方法接收一些输入
	def eval([user defined inputs]): Row = {
		val result = new Row(3)
		// Business processing based on data and parameters
		// 根据数据和参数进行业务处理,返回最终结果
		result
	}
	//指定结果对应的类型,例如这里 Row的类型,Row有三列
	override def getResultType(signature: Array[Class[_]]): 
	TypeInformation[_] = {
		Types.ROW(Types.STRING, Types.INT, Types.LONG)
	}
}

//使用 fun('e) 得到一个 Row 并定义名称 abc 然后获取 ac列
val res = tab
.map(fun('e)).as('a, 'b, 'c)
.select('a, 'c)

//好处:当你的列很多的时候,并且每一类都需要返回一个结果的时候
table.select(udf1(), udf2(), udf3()….) 
VS 
table.map(udf())

Map是输入一条输出一条

  ——FlatMap operation 易用性:

//方法签名:出入一个tableFunction 
def flatMap(tableFunction: Expression): Table
#tableFunction 实现的列子,返回一个 User类型,是一个 POJOs类型,Flink能够自动识别类型。
case class User(name: String, age: Int)
class MyFlatMap extends TableFunction[User] {
	def eval([user defined inputs]): Unit = {
		for(..){
			collect(User(name, age))
		}
	}
}

//使用
val res = tab
.flatMap(fun('e,'f)).as('name, 'age) 
.select('name, 'age)
Benefit

//好处
table.joinLateral(udtf) VS table.flatMap(udtf())

FlatMap 是输入一行输出多行

  ——Aggregate operation 易用性:

#方法签名:接收aggregateFunction
def aggregate(aggregateFunction: Expression): AggregatedTable
class AggregatedTable(table: Table, groupKeys: Seq[Expression], aggFunction: Expression)
#输入参数,定义aggregateFunction
class CountAccumulator extends JTuple1[Long] {
	f0 = 0L //count #刚开始初始化为0
}
#定义count的聚合函数
class CountAgg extends AggregateFunction[JLong, CountAccumulator]{
	def accumulate(acc: CountAccumulator): Unit = {
	acc.f0 += 1L
}
//返回最终结果
override def getValue(acc: CountAccumulator): JLong = {
	acc.f0
}
... retract()/merge()
}

//使用:
val res = tab
.groupBy('a)
.aggregate(agg('e,'f) as ('a, 'b, 'c))
.select('a, 'c)

//好处:
table.select(agg1(), agg2(), agg3()….)
VS 
table.aggregate(agg())

Aggregate operation 输入多行输出一行

  ——FlatAggregate operation 功能性:

#方法签名:输入 tableAggregateFunction 与 AggregateFunction 很相似
def flatAggregate(tableAggregateFunction: Expression): FlatAggregateTable
class FlatAggregateTable(table: Table, groupKey: Seq[Expression], tableAggFun: Expression)
class TopNAcc {
	var data: MapView[JInt, JLong] = _ // (rank -> value)
		...
	}
	class TopN(n: Int) extends TableAggregateFunction[(Int, Long), TopNAccum] {
		def accumulate(acc: TopNAcc, [user defined inputs]) {
		...
	}
        #可以那多 column,进行多个输出
	def emitValue(acc: TopNAcc, out: Collector[(Int, Long)]): Unit = {
		...
	}
	...retract/merge
}

#用法
val res = tab
.groupBy(‘a)
.flatAggregate(
flatAggFunc(‘e,’f) as (‘a, ‘b, ‘c))
.select(‘a, ‘c)

#好处
新增了一种agg,输出多行

FlatAggregate operation 输入多行输出多行

Aggregate 与 FlatAggregate 的区别:使用 Max 和 Top2的场景比较 Aggregate 和 FlatAggregate 之间的差别。如下有一张输入表,表有三列(ID、NAME、PRICE),然后对Price 求最大指和Top2。
Max 操作是蓝线,首先创建累加器,然后在累加器上 accumulate操作,例如6过去是6,3过去没有6大还是6等等。得到最终得到8的结果。
TOP2操作时红线,首先创建累加器,然后在累加器上 accumulate操作,例如6过去是6,3过去因为是两个元素所以3也保存,当5过来时,和最小的比较,3就被淘汰了等等。得到最终得到8和6的结果。

 总结:

 3、增强Table API功能性的操作;

猜你喜欢

转载自blog.csdn.net/zhengzhaoyang122/article/details/107239536