Verwendung der Java Table API von Flink

Entwickelt mit der Java Table API

Abhängigkeiten hinzufügen

Bei der Verwendung der Tabellen-API im Code müssen relevante Abhängigkeiten eingeführt werden

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-clients</artifactId>
    <version>${
    
    flink.version}</version>
</dependency>

<!--负责Table API和下层DataStream API的连接支持-->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table-api-java-bridge</artifactId>
    <version>${
    
    flink.version}</version>
</dependency>

 <!--在本地的集成开发环境里运行Table APISQL的支持-->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table-planner-loader</artifactId>
    <version>${
    
    flink.version}</version>
</dependency>

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table-runtime</artifactId>
    <version>${
    
    flink.version}</version>
</dependency>

作者:CodeDevMaster
链接:https://juejin.cn/spost/7299353390764163122
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Erstellen Sie eine Tabellenumgebung

Die Verwendung der Tabellen-API und von SQL erfordert eine spezielle Laufzeitumgebung, nämlich die TableEnvironment.

Haupteffekt:

1.注册Catalog和表

2.执行 SQL 查询

3.注册用户自定义函数(UDF)

4.DataStream 和表之间的转换

Jede Tabelle und SQL-Ausführung muss an eine Tabellenumgebung gebunden sein.

TableEnvironment ist die grundlegende Schnittstellenklasse, die in der Tabellen-API bereitgestellt wird. Sie erstellt eine Tabellenumgebungsinstanz durch Aufrufen der statischen Methode create(). Übergeben Sie einen Umgebungskonfigurationsparameter EnvironmentSettings, der den Ausführungsmodus und den Planer der aktuellen Tabellenumgebung angeben kann. Es gibt zwei Ausführungsmodi: Stapelverarbeitung und Stream-Verarbeitung. Der Standardwert ist der Stream-Verarbeitungsmodus; der Planer verwendet standardmäßig den Blink-Planer.

EnvironmentSettings settings = EnvironmentSettings
    .newInstance()
    .inStreamingMode()    // 使用流处理模式
    .build();

TableEnvironment tableEnv = TableEnvironment.create(setting);

Eine weitere, einfachere Möglichkeit, eine Tabellenumgebung zu erstellen:

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

Tabelle erstellen

So erstellen Sie eine Tabelle: über den Connector, virtuelle Tabellen, virtuelle Tabellen

1. Anschlusstabelle

Der intuitivste Weg, eine Tabelle zu erstellen, besteht darin, über einen Connector eine Verbindung zu einem externen System herzustellen und dann die entsprechende Tabellenstruktur zu definieren. Rufen Sie die Methode „executeSql()“ der Tabellenumgebung im Code auf und übergeben Sie eine DDL als Parameter, um die SQL-Operation auszuführen.

Beispiel: Übergeben Sie eine CREATE-Anweisung, um eine Tabelle zu erstellen, und geben Sie über das Schlüsselwort WITH den Connector an, der eine Verbindung zum externen System herstellen soll

tableEnv.executeSql("CREATE [TEMPORARY] TABLE MyTable ... WITH ( 'connector' = ... )");
tableEnv.executeSql("create table tb_test (f_sequence int,f_random int) WITH ('connector' ='print');");

2. Virtueller Tisch

Verwenden Sie diese Tabelle direkt für die Abfragekonvertierung in SQL, rufen Sie die Methode sqlQuery() der Tabellenumgebung auf, übergeben Sie eine SQL-Anweisung als Parameter zum Ausführen der Abfrage und das Ergebnis ist ein Tabellenobjekt.

Table newTable = tableEnv.sqlQuery("SELECT ... FROM MyTable... ");

Table ist die in der Table-API bereitgestellte Kernschnittstellenklasse und stellt eine in Java definierte Tabelleninstanz dar. Da das Tabellenobjekt nicht in der Tabellenumgebung registriert ist, müssen Sie, wenn Sie es direkt in SQL verwenden möchten, auch die Zwischenergebnistabelle in der Umgebung registrieren:

tableEnv.createTemporaryView("new_table", newTable);

tableEnv.sqlQuery("select * from new_table;");

Nachschlagwerk

Der Abfragevorgang für eine Tabelle entspricht der Konvertierungsverarbeitung von Stream-Daten. Flink bietet zwei Abfragemethoden: SQL und Tabellen-API

1. Führen Sie SQL zur Abfrage aus

Die Abfrage erhält ein neues Tabellenobjekt

Table table = tableEnv.sqlQuery("select * from tb;");

Sie können es erneut als virtuelle Tabelle registrieren, um es weiterhin in SQL aufzurufen

tableEnv.createTemporaryView("new_table", table);
tableEnv.sqlQuery("select * from new_table;");

Sie können die Abfrageergebnisse auch direkt in eine andere registrierte Tabelle schreiben. Sie müssen die MethodeexecuteSql() der Tabellenumgebung aufrufen, um DDL auszuführen und eine INSERT-Anweisung zu übergeben.

tableEnv.executeSql("insert into tb select * from new_table");

2. Rufen Sie die Tabellen-API zur Abfrage auf

Die Tabellen-API ist eine Abfrage-API, die in die Sprachen Java und Scala eingebettet ist. Der Kern ist die Tabellenschnittstellenklasse. Durch die schrittweise Verkettung der Tabellenmethode können alle Abfragekonvertierungsvorgänge definiert werden.

Basierend auf der registrierten Tabelle in der Umgebung ist der eingehende Parameter über die from()-Methode der Tabellenumgebung der registrierte Tabellenname, ein Tabellenobjekt wird abgerufen und die API wird aufgerufen, um verschiedene Konvertierungsvorgänge und eine neue Tabelle durchzuführen Objekt erhalten wird.

        Table source = tableEnv.from("tb");
        Table result = source
                // 查询条件
                .where($("f_sequence").isEqual(2))
                // 分组
                .groupBy($("f_sequence"))
                // 聚合 起别名
                .aggregate($("f_random").sum().as("sumValue"))
                // 查询返回字段
                .select($("f_sequence"), $("sumValue"));        

Ausgabetabelle

Die Erstellung und Abfrage von Tabellen entspricht der Lesedatenquelle Source und Transformation Transform in der Stream-Verarbeitung. Abschließend werden die Ergebnisdaten an das externe System ausgegeben, was der Ausgabeoperation der Tabelle entspricht.

Ergebnistabelle in registrierte Ausgabetabelle schreiben

// SQL方式
tableEnv.executeSql("insert into tb_test select f_sequence,f_random from tb_tmp");

// Table API方式
result.executeInsert("tb_test");

Anwendungsbeispiel

 public static void main(String[] args) {
    
    
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        /**
         * 创建表环境
         * 方式一
         */
//        EnvironmentSettings settings = EnvironmentSettings.newInstance().inStreamingMode().build();
//        StreamTableEnvironment tableEnv = TableEnvironment.create(settings);
        /**
         * 创建表环境
         * 方式二
         */
        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        // 创建表
        tableEnv.executeSql("CREATE TABLE datagen (\n" +
                "                f_sequence INT,\n" +
                "                f_random INT,\n" +
                "                f_random_str STRING,\n" +
                "                ts AS localtimestamp,\n" +
                "                WATERMARK FOR ts AS ts\n" +
                "        ) WITH (\n" +
                "        'connector' = 'datagen',\n" +
                "                'rows-per-second'='5',\n" +
                "        'fields.f_sequence.kind'='sequence',\n" +
                "        'fields.f_sequence.start'='1',\n" +
                "                'fields.f_sequence.end'='1000',\n" +
                "        'fields.f_random.min'='1',\n" +
                "                'fields.f_random.max'='1000',\n" +
                "        'fields.f_random_str.length'='10'\n" +
                ");");

        tableEnv.executeSql("create table tb_test (f_sequence int,f_random int) WITH ('connector' ='print');");


        // 使用sql方式查询
        Table table = tableEnv.sqlQuery("select f_sequence,f_random,f_random_str from datagen where f_sequence < 5;");
        // 把table对象,注册成表名
        tableEnv.createTemporaryView("tb_tmp", table);
        tableEnv.sqlQuery("select * from tb_tmp where f_sequence > 3");
        // 输出到另一张表
//        tableEnv.executeSql("insert into tb_test select f_sequence,f_random from tb_tmp");

        // table api方式
        Table source = tableEnv.from("datagen");
        Table result = source
                // 查询条件
                .where($("f_sequence").isEqual(2))
                // 分组
                .groupBy($("f_sequence"))
                // 聚合 起别名
                .aggregate($("f_random").sum().as("sumValue"))
                // 查询返回字段
                .select($("f_sequence"), $("sumValue"));
        // 输出到另一张表
        result.executeInsert("tb_test");
    }

Wie folgt: Ein komplexeres Tabellen-API-Programm scannt die Tabelle „Bestellungen“, filtert Nullwerte heraus, normalisiert Felder vom Typ „Zeichenfolge“, führt stündlich Berechnungen durch und gibt den durchschnittlichen Rechnungsbetrag von a zurück.

// 指定表程序
Table orders = tEnv.from("Orders");

Table result = orders
        .filter(
            and(
                $("a").isNotNull(),
                $("b").isNotNull(),
                $("c").isNotNull()
            ))
        .select($("a").lowerCase().as("a"), $("b"), $("rowtime"))
        .window(Tumble.over(lit(1).hours()).on($("rowtime")).as("hourlyWindow"))
        .groupBy($("hourlyWindow"), $("a"))
        .select($("a"), $("hourlyWindow").end().as("hour"), $("b").avg().as("avgBillingAmount"));

Tabellen- und Stream-Konvertierung

Konvertieren Sie den Stream DataStream in die Tabelle Table

1. Rufen Sie die fromDataStream()-Methode auf

        // 将数据流转换成表
        Table table = tableEnv.fromDataStream(dataStreamSource);
        
        // 指定提取哪些属性作为表中的字段名
        Table table1 = tableEnv.fromDataStream(dataStreamSource, $("id"), $("name"));

        // 通过表达式的as()方法对字段进行重命名
        Table table2 = tableEnv.fromDataStream(dataStreamSource, $("id").as("uid"), $("name").as("username"));

2. Rufen Sie createTemporaryView() auf

Verweisen Sie direkt in SQL auf diese Tabelle und rufen Sie die Methode createTemporaryView() der Tabellenumgebung auf, um eine virtuelle Ansicht zu erstellen

tableEnv.createTemporaryView("new_table", table);
        /**
         * String 注册的表名
         * DataStream<T> DataStream
         * Expression 指定表中的字段
         */
        tableEnv.createTemporaryView("new_table",dataStreamSource, $("id").as("uid"),$("name"));

Konvertieren Sie die Tabelle Table in den Stream DataStream

1. Rufen Sie toDataStream() auf

// 将数据流转换成表
Table table = tableEnv.fromDataStream(dataStreamSource);

DataStream<Row> dataStream = tableEnv.toDataStream(table);
dataStream.print();

2. Rufen Sie toChangelogStream() auf

// 将表转换成更新日志流
 DataStream<Row> dataStream = tableEnv.toChangelogStream(table);
dataStream.print();

Beispiel

    public static void main(String[] args) throws Exception {
    
    
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStreamSource<Tuple2<String, Integer>> dataStreamSource = env.fromElements(Tuple2.of("a", 1), Tuple2.of("b", 2), Tuple2.of("c", 3), Tuple2.of("d", 4));

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        // 流转表
        Table table = tableEnv.fromDataStream(dataStreamSource);
        tableEnv.createTemporaryView("newTable", table);

        Table filterTable = tableEnv.sqlQuery("select f0,f1 from newTable where f1>2");
        Table sumTable = tableEnv.sqlQuery("select f0,sum(f1) from newTable group by f0");

        // 表转流

        // 追加流
        tableEnv.toDataStream(filterTable, Row.class).print("stream-1");
        // 将表转换成更新日志流
        tableEnv.toChangelogStream(sumTable).print("stream-2");


        // 注意:只要调用DataStreamAPI,就需要调用execute
        env.execute();
    }
stream-1:6> +I[d, 4]
stream-1:5> +I[c, 3]
stream-2:2> +I[d, 4]
stream-2:1> +I[a, 1]
stream-2:7> +I[c, 3]
stream-2:1> +I[b, 2]

Art der Daten

Es besteht ein Problem bei der Datentypkonvertierung zwischen DataStream und Table. Im Folgenden sind einige gängige Datentypen aufgeführt. Weitere Informationen finden Sie in der Dokumentation: Datentypen

1.Tupeltyp

Table支持Flink中定义的元组类型Tuple,对应在表中字段名默认就是元组中元素的属性名f0、f1、f2...

所有字段都可以重新排序  可以提取其中的一部分字段    可以通过调用表达式的as()方法来进行重命名

2.POJO-Typ

如果不指定字段名称,直接使用原始POJO类型中的字段名称

POJO中的字段同样可以被重新排序、提却和重命名

3.ROW-Typ

通用的数据类型行Row,它是Table中数据的基本组织形式
// 流转表
Table table = tableEnv.fromDataStream(dataStreamSource, $("f1").as("num"), $("f0").as("key"));
tableEnv.createTemporaryView("newTable", table);
 Table filterTable = tableEnv.sqlQuery("select num,key from newTable where num > 2");

// 表转流
tableEnv.toDataStream(filterTable, Row.class).print("stream-1");        
// 流转表
Table table = tableEnv.fromDataStream(dataStreamSource, $("id").as("uid"), $("name").as("username"));
tableEnv.createTemporaryView("newTable", table);
Table filterTable = tableEnv.sqlQuery("select uid,name from newTable where uid > 2");

// 表转流
tableEnv.toDataStream(filterTable, User.class).print("stream-1");

Benutzerdefinierte Funktion UDF

Es ist unmöglich, dass Systemfunktionen alle Funktionen abdecken. Wenn Systemfunktionen Anforderungen nicht unterstützen, müssen benutzerdefinierte Funktionen verwendet werden, um sie zu implementieren.

Die Tabellen-API und SQL von Flink bieten eine Vielzahl benutzerdefinierter Funktionsschnittstellen, die in Form abstrakter Klassen definiert sind.

Es gibt hauptsächlich folgende Kategorien:

标量函数(Scalar Functions):将输入的标量值转换成一个新的标量值

表函数(Table Functions):将标量值转换成一个或多个新的行数据,也就是扩展成一个表

聚合函数(Aggregate Functions):将多行数据里的标量值转换成一个新的标量值

表聚合函数(Table Aggregate Functions):将多行数据里的标量值转换成一个或多个新的行数据

1. Funktion registrieren

tableEnv.createTemporarySystemFunction("MyFunction", MyFunction.class);

2. Verwenden Sie die Tabellen-API, um Funktionen aufzurufen

# 使用call()方法来调用自定义函数: 1.注册的函数名 2.函数调用时本身的参数
tableEnv.from("MyTable").select(call("MyFunction", $("myField")));

3. Rufen Sie Funktionen in SQL auf

tableEnv.sqlQuery("SELECT MyFunction(myField) FROM MyTable");

Skalarfunktion

Skalarfunktionen sind Funktionen, die Berechnungen für Eingabeargumente durchführen und einen einzelnen Wert zurückgeben. In Flink SQL können Sie integrierte Skalarfunktionen oder benutzerdefinierte Skalarfunktionen verwenden, um Ausdrücke und Datenkonvertierungsvorgänge zu verarbeiten.

Beispiel einer integrierten Skalarfunktion:

Verwenden Sie die integrierte Skalarfunktion UPPER, um das Namensfeld in Großbuchstaben umzuwandeln, und verwenden Sie die Skalarfunktion LENGTH, um die Länge des Beschreibungsfelds zu berechnen.

SELECT id, UPPER(name) as uppercase_name, LENGTH(description) as description_length
FROM myTable

Beispiel für eine benutzerdefinierte Skalarfunktion:

自定义标量函数可以把0个、1个或多个标量值转换成一个标量值,它对应的输入是一行数据中的字段,输出则是唯一的值。是一个一对一的转换关系。

自定义一个类来继承抽象类ScalarFunction,并实现叫作eval() 的求值方法。

标量函数的行为就取决于求值方法的定义,它必须是公有的(public),而且名字必须是eval。

求值方法eval可以重载多次,任何数据类型都可作为求值方法的参数和返回值类型。

注意:ScalarFunction抽象类中并没有定义eval()方法,所以不能直接在代码中重写override;但Table API的框架底层又要求了求值方法必须名字为eval()。
    public static void main(String[] args) throws Exception {
    
    
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStreamSource<Tuple2<String, Integer>> dataStreamSource = env.fromElements(Tuple2.of("a", 1), Tuple2.of("b", 2), Tuple2.of("c", 3), Tuple2.of("d", 4));

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
        Table table = tableEnv.fromDataStream(dataStreamSource);
        tableEnv.createTemporaryView("newTable", table);

        // 注册函数
        tableEnv.createTemporaryFunction("MyScalarFunction", MyScalarFunction.class);

        // 调用自定义函数
        // sql用法
        tableEnv.sqlQuery("select MyScalarFunction(f0,1) from newTable")
                .execute()  // 调用execute,就不需要调用env.execute()
                .print();

        // table api用法
        table.select(call("MyScalarFunction", $("f1"), 1))
                .execute()
                .print();


    }

    /**
     * 自定义函数的实现类
     * DataTypeHint(inputGroup = InputGroup.ANY)对输入参数的类型做标注,表示参数可以是任意类型
     * 参数1:接受任意类型的输入 参数2:要求int类型
     * 返回INT型的求和输出
     */
    public static class MyScalarFunction extends ScalarFunction {
    
    
        public int eval(@DataTypeHint(inputGroup = InputGroup.ANY) Object a, int b) {
    
    
            return a.hashCode() + b;
        }
    }
+----+-------------+
| op |      EXPR$0 |
+----+-------------+
| +I |          98 |
| +I |          99 |
| +I |         100 |
| +I |         101 |
+----+-------------+
4 rows in set
+----+-------------+
| op |         _c0 |
+----+-------------+
| +I |           2 |
| +I |           3 |
| +I |           4 |
| +I |           5 |
+----+-------------+
4 rows in set

Tabellenfunktion

Tabellenfunktionen sind Funktionen, die eine oder mehrere Eingabezeilen akzeptieren und als Ergebnis eine neue Tabelle zurückgeben. In Flink SQL können Sie integrierte Tabellenfunktionen oder benutzerdefinierte Tabellenfunktionen für Tabellenoperationen und Datentransformationen verwenden.

Beispiel einer integrierten Tabellenfunktion:

Verwenden Sie die integrierte EXPLODE-Funktion, um das Satzfeld durch Leerzeichen aufzuteilen und eine neue Tabelle als T zu erstellen, in der jedes Wort eine Zeile ist. Die Wörter können dann mit anderen Feldern aus der Originaltabelle kombiniert oder gefiltert werden.

SELECT id, word
FROM myTable, LATERAL TABLE(EXPLODE(split(sentence, ' '))) as T(word)

Beispiel für eine benutzerdefinierte Tabellenfunktion:

表函数的输入参数可以是 0个、1个或多个标量值,它可以返回任意多行数据。

表函数可以认为就是返回一个表的函数,是一个一对多的转换关系。


自定义表函数,需要自定义类来继承抽象类TableFunction,内部必须要实现一个名为eval 的求值方法。

TableFunction类本身是有一个泛型参数T,是表函数返回数据的类型

eval()方法没有返回类型,内部也没有return语句,是通过调用collect()方法来发送想要输出的行数据

在SQL中调用表函数,需要使用LATERAL TABLE(<TableFunction>)来生成扩展的侧向表,然后与原始表进行联结
public static void main(String[] args) throws Exception {
    
    
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();


        DataStreamSource<String> dataStreamSource = env.fromElements("java", "python", "flink");

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        Table table = tableEnv.fromDataStream(dataStreamSource, $("world"));
        tableEnv.createTemporaryView("wordTable", table);

        // 注册函数
        tableEnv.createTemporaryFunction("MyTableFunction", MyTableFunction.class);

        // 调用自定义函数

        // 交叉联结
        tableEnv.sqlQuery("select world,wordUpperCase,length from wordTable,lateral table(MyTableFunction(world))")
                .execute()
                .print();

        // 带 on true 条件的左联结
        tableEnv.sqlQuery("select world,wordUpperCase,length from wordTable left join lateral table(MyTableFunction(world)) on true")
                .execute()
                .print();

        // 对侧向表的中字段进行重命名
        tableEnv.sqlQuery("select world,newWord,newLength from wordTable left join lateral table(MyTableFunction(world))  as T(newWord,newLength) on true")
                .execute()
                .print();
    }


    /**
     * Row包含两个字段:word和length
     * 将表函数的输出类型定义成ROW,得到侧向表中的数据类型;每行数据转换后也只有一行
     * 表字段起别名:
     * 调用表函数:lateral table(MyTableFunction(world)) as T(a, b)
     * 使用注解: @FunctionHint(output = @DataTypeHint("ROW<word STRING,length INT>"))
     */
    @FunctionHint(output = @DataTypeHint("ROW<wordUpperCase STRING,length INT>"))
    public static class MyTableFunction extends TableFunction<Row> {
    
    
        public void eval(String str) {
    
    
            Row row = new Row(2);
            row.setField(0, str.toUpperCase());
            row.setField(1, str.length());
            collect(row);
        }
    }

+----+--------------------------------+--------------------------------+-------------+
| op |                          world |                  wordUpperCase |      length |
+----+--------------------------------+--------------------------------+-------------+
| +I |                           java |                           JAVA |           4 |
| +I |                         python |                         PYTHON |           6 |
| +I |                          flink |                          FLINK |           5 |
+----+--------------------------------+--------------------------------+-------------+
3 rows in set
+----+--------------------------------+--------------------------------+-------------+
| op |                          world |                  wordUpperCase |      length |
+----+--------------------------------+--------------------------------+-------------+
| +I |                           java |                           JAVA |           4 |
| +I |                         python |                         PYTHON |           6 |
| +I |                          flink |                          FLINK |           5 |
+----+--------------------------------+--------------------------------+-------------+
3 rows in set
+----+--------------------------------+--------------------------------+-------------+
| op |                          world |                        newWord |   newLength |
+----+--------------------------------+--------------------------------+-------------+
| +I |                           java |                           JAVA |           4 |
| +I |                         python |                         PYTHON |           6 |
| +I |                          flink |                          FLINK |           5 |
+----+--------------------------------+--------------------------------+-------------+
3 rows in set

Aggregatfunktion

Aggregationsfunktionen (Aggregate Functions) sind eine Art von Funktionen, die zur Durchführung von Aggregationsberechnungen für Daten verwendet werden. In Flink SQL können Sie integrierte Aggregatfunktionen verwenden, um zusammenfassende Ergebnisse eines Datensatzes zu berechnen.

Beispiel einer integrierten Aggregatfunktion:

Verwenden Sie die SUM-Funktion, um die Summe des Gehaltsfelds zu berechnen, die COUNT-Funktion, um die Gesamtzahl der Mitarbeiter zu berechnen, und die AVG-Funktion, um den Durchschnitt des Altersfelds zu berechnen.

SELECT SUM(salary) as total_salary, COUNT(*) as employee_count, AVG(age) as avg_age
FROM employees

Beispiel für eine benutzerdefinierte Aggregatfunktion:

自定义聚合函数会把一行或多行数据聚合成一个标量值。是一个标准的多对一的转换。

自定义聚合函数需要继承抽象类AggregateFunction。有两个泛型参数<T, ACC>,T表示聚合输出的结果类型,ACC表示聚合的中间状态类型。

So funktionieren Aggregatfunktionen:

创建一个累加器accumulator,用来存储聚合的中间结果。累加器可以看作是一个聚合状态。调用createAccumulator()方法可以创建一个空的累加器

对于输入的每一行数据,都会调用accumulate()方法来更新累加器,这是聚合的核心过程

当所有的数据都处理完之后,调用getValue()方法来计算并返回最终的结果
  public static void main(String[] args) throws Exception {
    
    
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();


        DataStreamSource<Tuple3<String, Integer, Integer>> dataStreamSource = env.fromElements(Tuple3.of("a", 1, 2), Tuple3.of("b", 2, 3), Tuple3.of("b", 3, 4), Tuple3.of("a", 4, 5));

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
        Table table = tableEnv.fromDataStream(dataStreamSource, $("f0").as("key"), $("f1").as("a"), $("f2").as("b"));
        tableEnv.createTemporaryView("newTable", table);

        tableEnv.createTemporaryFunction("MyAggregateFunction", MyAggregateFunction.class);
        tableEnv.sqlQuery("select key,MyAggregateFunction(a,b) as keyValue  from newTable group by key")
                .execute()
                .print();
    }


    /**
     * 自定义聚合函数,继承AggregateFunction<返回类型,累加器类型>
     */
    public static class MyAggregateFunction extends AggregateFunction<Double, Tuple2<Integer, Integer>> {
    
    

        /**
         * 创建累加器的方法
         * 没有输入参数,返回类型为累加器类型ACC
         *
         * @return
         */
        @Override
        public Tuple2<Integer, Integer> createAccumulator() {
    
    
            return Tuple2.of(0, 0);
        }

        /**
         * 进行聚合计算的核心方法,每来一行数据都会调用
         * <p>
         * 第一个参数: 当前累加器,类型为ACC,表示当前聚合的中间状态
         * 后面参数: 聚合函数调用时传入的参数,可以有多个,类型也可以不同
         * <p>
         * 主要功能:更新聚合状态,没有返回类型
         * <p>
         * 必须为public,方法名必须为accumulate,且不能直接override、只能手动实现
         *
         * @param acc 累加器类型
         * @param a   第一个参数
         * @param b   第二个参数
         */
        public void accumulate(Tuple2<Integer, Integer> acc, Integer a, Integer b) {
    
    
            acc.f0 += a;
            acc.f1 += b;
        }


        /**
         * 返回结果的方法
         * 输入参数是ACC类型的累加器
         * 输出类型为T
         * 在遇到复杂类型时,Flink类型推导可能会无法得到正确的结果:可以通过 getAccumulatorType()和getResultType()两个方法来指定
         *
         * @return
         */
        @Override
        public Double getValue(Tuple2<Integer, Integer> acc) {
    
    
            return (acc.f0 + acc.f1) * 1D / 2;
        }
    }
+----+--------------------------------+--------------------------------+
| op |                            key |                       keyValue |
+----+--------------------------------+--------------------------------+
| +I |                              a |                            1.5 |
| +I |                              b |                            2.5 |
| -U |                              b |                            2.5 |
| +U |                              b |                            6.0 |
| -U |                              a |                            1.5 |
| +U |                              a |                            6.0 |
+----+--------------------------------+--------------------------------+
6 rows in set

Tabellenaggregatfunktion

Tabellenaggregationsfunktionen sind eine Art von Funktion, mit der Aggregationsberechnungen für Tabellendaten durchgeführt werden. Sie können die gesamte Tabelle oder eine Teilmenge der Tabelle in einer Aggregationsoperation verarbeiten und die aggregierten Ergebnisse zurückgeben.

Sie können Tabellenaggregationsfunktionen verwenden, um komplexere Aggregationsvorgänge durchzuführen, z. B. Berechnungen nach Gruppen, Berechnungen nach Zeitfenstern usw.

1.GROUP BY-Aggregation:

Gruppieren Sie die Mitarbeitertabelle nach dem Abteilungsfeld und berechnen Sie den Gesamtumsatz und das Durchschnittsgehalt für jede Abteilung

SELECT department, SUM(sales) as total_sales, AVG(salary) as avg_salary
FROM employees
GROUP BY department

2. Zeitfensteraggregation:

Verwenden Sie die TUMBLE-Funktion, um das Feld „order_time“ in 1-Stunden-Fenster zu unterteilen und die Bestellmenge und den Gesamtumsatz innerhalb jedes Fensters zu berechnen.

SELECT TUMBLE_START(order_time, INTERVAL '1' HOUR) as window_start,
       COUNT(*) as order_count,
       SUM(order_amount) as total_amount
FROM orders
GROUP BY TUMBLE(order_time, INTERVAL '1' HOUR)

Benutzerdefinierte Tabellenaggregationsfunktion:

表聚合函数(UDTAGG)可以把一行或多行数据聚合成另一张表,结果表中可以有多行多列。是一个多对多的转换。

自定义表聚合函数需要继承抽象类TableAggregateFunction。

TableAggregateFunction的结构和原理与AggregateFunction非常类似,同样有两个泛型参数<T, ACC>,用一个ACC类型的累加器accumulator来存储聚合的中间结果。
    public static void main(String[] args) throws Exception {
    
    
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();


        DataStreamSource<Tuple2<String, Integer>> dataStreamSource = env.fromElements(Tuple2.of("a", 1), Tuple2.of("b", 2), Tuple2.of("a", 3), Tuple2.of("b", 4));

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        Table table = tableEnv.fromDataStream(dataStreamSource, $("f0").as("key"), $("f1").as("value"));
        tableEnv.createTemporaryFunction("MyTableAggregateFunction", MyTableAggregateFunction.class);

        // 能用Table API
        table.flatAggregate(call("MyTableAggregateFunction", $("key"), $("value")))
                .select($("f0").as("newKey"), $("f1").as("newValue"))
                .execute().print();


    }


    /**
     * 继承 TableAggregateFunction<返回类型,累加器类型>
     */
    public static class MyTableAggregateFunction extends TableAggregateFunction<Tuple2<String, Integer>, Map<String, Integer>> {
    
    

        /**
         * 创建累加器
         *
         * @return
         */
        @Override
        public Map<String, Integer> createAccumulator() {
    
    
            Map<String, Integer> map = new HashMap<>();
            return map;
        }


        /**
         * 聚合计算的核心 每来一个数据调用一次
         *
         * @param acc   累加器
         * @param key   第一个参数
         * @param value 第二个参数
         */
        public void accumulate(Map<String, Integer> acc, String key, Integer value) {
    
    
            if (acc.containsKey(key)) {
    
    
                acc.put(key, acc.get(key) + value);
            } else {
    
    
                acc.put(key, value);
            }
        }

        /**
         * 输出结果
         * <p>
         * 所有输入行处理完成后,输出最终计算结果的方法
         * <p>
         * emitValue没有输出类型,而输入参数有两个:1.ACC类型的累加器,2.用于输出数据的收集器out,它的类型为Collect<T>
         * <p>
         * emitValue()在抽象类中没有定义,无法override,必须手动实现
         *
         * @param acc 累加器
         * @param out 采集器<返回类型>
         */
        public void emitValue(Map<String, Integer> acc, Collector<Tuple2<String, Integer>> out) {
    
    
            acc.entrySet().stream().forEach(item -> {
    
    
                out.collect(Tuple2.of(item.getKey(), item.getValue()));
            });
        }
    }
+----+--------------------------------+-------------+
| op |                         newKey |    newValue |
+----+--------------------------------+-------------+
| +I |                              a |           1 |
| -D |                              a |           1 |
| +I |                              a |           1 |
| +I |                              b |           2 |
| -D |                              a |           1 |
| -D |                              b |           2 |
| +I |                              a |           4 |
| +I |                              b |           2 |
| -D |                              a |           4 |
| -D |                              b |           2 |
| +I |                              a |           4 |
| +I |                              b |           6 |
+----+--------------------------------+-------------+
12 rows in set

Zusammenfassung der API-Methoden

grundlegende Methode

Aus

Führen Sie einen Scan einer registrierten Tabelle durch

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

FromValues

Erstellt eine Inline-Tabelle basierend auf den bereitgestellten Zeilen. Zusammengesetzte Zeilen können mit dem Ausdruck row(…) erstellt werden

Table table = tEnv.fromValues(
   row(1, "ABC"),
   row(2L, "ABCDE")
);

Struktur generieren:

f0: BIGINT NOT NULL
f1: VARCHAR(5) NOT NULL

Beachten:

Die Methode ruft den Typ automatisch basierend auf dem Eingabeausdruck ab. Wenn die Typen an einem bestimmten Ort inkonsistent sind, versucht die Methode, einen gemeinsamen Supertyp für alle Typen zu finden. Wenn der öffentliche Supertyp nicht vorhanden ist, wird eine Ausnahme ausgelöst.

Geben Sie den erforderlichen Typ explizit an:

Table table = tEnv.fromValues(
    DataTypes.ROW(
        DataTypes.FIELD("id", DataTypes.DECIMAL(10, 2)),
        DataTypes.FIELD("name", DataTypes.STRING())
    ),
    row(1, "ABC"),
    row(2L, "ABCDE")
);

Tabellenstruktur generieren:

id: DECIMAL(10, 2)
name: STRING

Wählen

Führen Sie einen Auswahlvorgang aus

Table orders = tableEnv.from("Orders");
Table result = orders.select($("a"), $("c").as("d"));
Table result = orders.select($("*"));

Als

Feld umbenennen

Table orders = tableEnv.from("Orders");
Table result = orders.as("x, y, z, t");

Wo / Filter

Filtern Sie Zeilen heraus, die nicht durch das Filterprädikat validiert werden

Table orders = tableEnv.from("Orders");
Table result = orders.where($("b").isEqual("red"));
Table result = orders.filter($("b").isEqual("red"));

einfügen in

Diese Methode führt eine Einfügung in eine registrierte Ausgabetabelle durch und konvertiert INSERT INTO in eine TablePipeline. Dieser Datenfluss kann mit TablePipeline.explain() erklärt und mit TablePipeline.execute() ausgeführt werden.

Die Ausgabetabelle muss im TableEnvironment registriert sein. Darüber hinaus muss das Schema der registrierten Tabelle mit dem Schema in der Abfrage übereinstimmen.

Table orders = tableEnv.from("Orders");
orders.insertInto("OutOrders").execute();

Spaltenoperationen

1.Spalten hinzufügen

Führen Sie Feldadditionsoperationen durch. Wenn das hinzugefügte Feld bereits vorhanden ist, wird eine Ausnahme ausgelöst.

Table result = orders.addColumns(concat($("c"), "sunny"));

2.addOrReplaceColumns

Führen Sie Feldadditionsoperationen durch. Wenn die hinzugefügte Spalte denselben Namen wie eine vorhandene Spalte hat, wird das vorhandene Feld ersetzt. Wenn in den hinzugefügten Feldern doppelte Feldnamen vorhanden sind, wird außerdem das letzte Feld verwendet.

Table result = orders.addOrReplaceColumns(concat($("c"), "sunny").as("desc"));

3.dropColumns

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

4.Spalten umbenennen

Führen Sie Feldumbenennungsvorgänge durch. Feldausdrücke sollten Aliasausdrücke sein und können nur umbenannt werden, wenn das Feld bereits vorhanden ist.

Table result = orders.renameColumns($("b").as("b2"), $("c").as("c2"));

Aggregationsoperationen

1.groupBy

Zeilen werden mithilfe des Gruppierungsschlüssels gruppiert und der zugehörige Aggregationsoperator wird zum Aggregieren der Zeilen nach Gruppe verwendet.

Table orders = tableEnv.from("Orders");
Table result = orders.groupBy($("a")).select($("a"), $("b").sum().as("d"));

2.Gruppieren nach Fenster

Verwenden Sie das Gruppierungsfenster, um Tabellen mithilfe eines oder mehrerer Gruppierungsschlüssel zu gruppieren und zusammenzufassen.

Table result = orders
    .window(Tumble.over(lit(5).minutes()).on($("rowtime")).as("w")) // 定义窗口
    .groupBy($("a"), $("w")) // 按窗口和键分组
    // 访问窗口属性并聚合
    .select(
        $("a"),
        $("w").start(),
        $("w").end(),
        $("w").rowtime(),
        $("b").sum().as("d")
    );

3. Über dem Fenster

Alle Aggregationen müssen im selben Fenster definiert werden, z. B. in derselben Partition, Sortierung und demselben Bereich. Derzeit wird nur PRECEDING zu Fenstern des aktuellen Zeilenbereichs (unbegrenzt oder begrenzt) unterstützt. Die ORDER BY-Operation muss ein einzelnes Zeitattribut angeben.

Table result = orders
    // 定义窗口
    .window(
        Over
          .partitionBy($("a"))
          .orderBy($("rowtime"))
          .preceding(UNBOUNDED_RANGE)
          .following(CURRENT_RANGE)
          .as("w"))
    // 滑动聚合
    .select(
        $("a"),
        $("b").avg().over($("w")),
        $("b").max().over($("w")),
        $("b").min().over($("w"))
    );

4.Eindeutig

Ähnlich der SQL-Aggregatklausel DISTINCT, z. B. COUNT(DISTINCT a). Ein eindeutiges Aggregat deklariert eine Aggregatfunktion (integriert oder benutzerdefiniert), die nur auf Eingabewerte angewendet wird, die sich voneinander unterscheiden.

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

// 按属性分组后的的互异(互不相同、去重)聚合
Table groupByDistinctResult = orders
    .groupBy($("a"))
    .select($("a"), $("b").sum().distinct().as("d"));
    
// 按属性、时间窗口分组后的互异(互不相同、去重)聚合
Table groupByWindowDistinctResult = orders
    .window(Tumble
            .over(lit(5).minutes())
            .on($("rowtime"))
            .as("w")
    )
    .groupBy($("a"), $("w"))
    .select($("a"), $("b").sum().distinct().as("d"));
    
// over window 上的互异(互不相同、去重)聚合
Table result = orders
    .window(Over
        .partitionBy($("a"))
        .orderBy($("rowtime"))
        .preceding(UNBOUNDED_RANGE)
        .as("w"))
    .select(
        $("a"), $("b").avg().distinct().over($("w")),
        $("b").max().over($("w")),
        $("b").min().over($("w"))
    );

// 对 user-defined aggregate functions 使用互异(互不相同、去重)聚合
tEnv.registerFunction("myUdagg", new MyUdagg());
orders.groupBy("users")
    .select(
        $("users"),
        call("myUdagg", $("points")).distinct().as("myDistinctResult")
    );

Table result = orders.distinct();

Tritt bei

1.Inner Join

Verknüpfen Sie zwei Tabellen. Die beiden Tabellen müssen unterschiedliche Feldnamen haben und mindestens ein Join-Gleichheits-Join-Prädikat muss über einen Join-Operator oder mithilfe eines Where- oder Filteroperators definiert werden.

Table left = tableEnv.from("MyTable").select($("a"), $("b"), $("c"));
Table right = tableEnv.from("MyTable").select($("d"), $("e"), $("f"));
Table result = left.join(right)
    .where($("a").isEqual($("d")))
    .select($("a"), $("b"), $("e"));

2. Äußerer Join

Ähnlich der SQL LEFT/RIGHT/FULL OUTER JOIN-Klausel. Verknüpfen Sie zwei Tabellen. Die beiden Tabellen müssen unterschiedliche Feldnamen haben und es muss mindestens ein Gleichheits-Join-Prädikat definiert sein.

Table left = tableEnv.from("MyTable").select($("a"), $("b"), $("c"));
Table right = tableEnv.from("MyTable").select($("d"), $("e"), $("f"));

Table leftOuterResult = left.leftOuterJoin(right, $("a").isEqual($("d")))
                            .select($("a"), $("b"), $("e"));
Table rightOuterResult = left.rightOuterJoin(right, $("a").isEqual($("d")))
                            .select($("a"), $("b"), $("e"));
Table fullOuterResult = left.fullOuterJoin(right, $("a").isEqual($("d")))
                            .select($("a"), $("b"), $("e"));

3.Intervallverknüpfung

Eine Teilmenge regulärer Joins, die im Streaming-Modus verarbeitet werden können und mindestens ein Equi-Join-Prädikat und eine Join-Bedingung erfordern, die die Zeitgrenzen beider Parteien begrenzt. Eine solche Bedingung kann durch zwei geeignete Bereichsprädikate (<, <=, >=, >) oder ein Äquivalenzprädikat definiert werden, das zwei Eingabetabellen für dasselbe Zeitattribut (d. h. Verarbeitungszeit oder Ereigniszeit) vergleicht.

Table left = tableEnv.from("MyTable").select($("a"), $("b"), $("c"), $("ltime"));
Table right = tableEnv.from("MyTable").select($("d"), $("e"), $("f"), $("rtime"));

Table result = left.join(right)
  .where(
    and(
        $("a").isEqual($("d")),
        $("ltime").isGreaterOrEqual($("rtime").minus(lit(5).minutes())),
        $("ltime").isLess($("rtime").plus(lit(10).minutes()))
    ))
  .select($("a"), $("b"), $("e"), $("ltime"));

4.Inner Join mit Tabellenfunktion

Die Ergebnisse von Join-Tabellen und Tabellenfunktionen. Jede Zeile der linken (äußeren) Tabelle wird mit allen Zeilen verknüpft, die durch den entsprechenden Aufruf der Funktion „Tabelle verbinden“ erstellt wurden. Wenn der Tabellenfunktionsaufruf ein leeres Ergebnis zurückgibt, löschen Sie eine Zeile aus der linken (äußeren) Tabelle.

// 注册 User-Defined Table Function
TableFunction<Tuple3<String,String,String>> split = new MySplitUDTF();
tableEnv.registerFunction("split", split);

// join
Table orders = tableEnv.from("Orders");
Table result = orders
    .joinLateral(call("split", $("c")).as("s", "t", "v"))
    .select($("a"), $("b"), $("s"), $("t"), $("v"));

5. Linker äußerer Join mit Tabellenfunktion

Die Ergebnisse von Join-Tabellen und Tabellenfunktionen. Jede Zeile der linken (äußeren) Tabelle verbindet alle Zeilen, die sich aus dem entsprechenden Aufruf der Funktion „Tabelle verbinden“ ergeben. Wenn der Tabellenfunktionsaufruf ein Nullergebnis zurückgibt, bleibt die entsprechende äußere Zeile (Outer Join) erhalten und das rechte Ergebnis wird mit Nullwerten gefüllt. Derzeit kann das Prädikat eines linken äußeren Joins einer Tabellenfunktion nur leer oder literal (konstant) wahr sein.

// join
Table orders = tableEnv.from("Orders");
Table result = orders
    .leftOuterJoinLateral(call("split", $("c")).as("s", "t", "v"))
    .select($("a"), $("b"), $("s"), $("t"), $("v"));

6. Mit Temporal Table verbinden

Eine Temporaltabelle ist eine Tabelle, die Änderungen im Laufe der Zeit verfolgt und Zugriff auf den Status der Temporaltabelle zu einem bestimmten Zeitpunkt bietet. Die Syntax zum Verknüpfen einer Tabelle mit der temporalen Tabellenfunktion ist dieselbe wie die Syntax für den inneren Join mithilfe der Tabellenfunktion. Derzeit werden nur Inner Joins mit temporalen Tabellen unterstützt.

Table ratesHistory = tableEnv.from("RatesHistory");

// 注册带有时间属性和主键的 temporal table function
TemporalTableFunction rates = ratesHistory.createTemporalTableFunction(
    "r_proctime",
    "r_currency");
tableEnv.registerFunction("rates", rates);

// 基于时间属性和键与“Orders”表关联
Table orders = tableEnv.from("Orders");
Table result = orders
    .joinLateral(call("rates", $("o_proctime")), $("o_currency").isEqual($("r_currency")));

Zusammenführungsvorgang

Union und UnionAll

Union zwei Tabellen löschen doppelte Datensätze, UnionAll jedoch nicht. Beide Tabellen müssen den gleichen Feldtyp haben.

Table left = tableEnv.from("orders1");
Table right = tableEnv.from("orders2");

left.union(right);
left.unionAll(right);

Intersect与IntersectAll

Intersect gibt Datensätze zurück, die in beiden Tabellen vorhanden sind. Wenn ein Datensatz in einer oder beiden Tabellen mehrfach vorhanden ist, wird nur ein Datensatz zurückgegeben.

IntersectAll gibt Datensätze zurück, die in beiden Tabellen vorhanden sind. Wenn ein Datensatz in zwei Tabellen mehrfach vorkommt, ist die Anzahl der zurückgegebenen Datensätze dieselbe wie die Häufigkeit, mit der der Datensatz in beiden Tabellen vorkommt.

Beide Tabellen müssen den gleichen Feldtyp haben.

left.intersect(right);

left.intersectAll(right);

Minus und MinusAll

Minus gibt Datensätze zurück, die in der linken Tabelle vorhanden sind, aber nicht in der rechten Tabelle. Doppelte Datensätze in der linken Tabelle werden nur einmal zurückgegeben.

MinusAll gibt Datensätze zurück, die in der rechten Tabelle nicht vorhanden sind. Ein Datensatz, der n-mal in der linken Tabelle und m-mal in der rechten Tabelle erscheint, erscheint (n - m)-mal in der Ergebnistabelle.

Beide Tabellen müssen den gleichen Feldtyp haben

left.minus(right);

left.minusAll(right);

IN

Die In-Klausel gibt true zurück, wenn der Wert des Ausdrucks in der Unterabfrage für die angegebene Tabelle vorhanden ist. Die Unterabfragetabelle muss aus einer Spalte bestehen. Diese Spalte muss vom gleichen Datentyp sein wie der Ausdruck.

Table result = left.select($("a"), $("b"), $("c")).where($("a").in(right));

Sortieren

Sortieren nach

Gibt einen global geordneten Datensatz über alle parallelen Partitionen zurück. Bei unbegrenzten Tabellen erfordert dieser Vorgang das Sortieren des Zeitattributs oder die Durchführung eines nachfolgenden Abrufvorgangs.

Table result = tab.orderBy($("a").asc());

Offset & Bringen

Die Offset-Operation begrenzt die (möglicherweise sortierte) Ergebnismenge basierend auf Offset-Positionen. Die Fetch-Operation beschränkt die (möglicherweise sortierte) Ergebnismenge auf die ersten n Zeilen. Normalerweise geht beiden Operationen eine Sortieroperation voraus. Bei unbegrenzten Tabellen erfordert die Offset-Operation eine Abrufoperation.

// 从已排序的结果集中返回前5条记录
Table result1 = in.orderBy($("a").asc()).fetch(5);

// 从已排序的结果集中返回跳过3条记录之后的所有记录
Table result2 = in.orderBy($("a").asc()).offset(3);

// 从已排序的结果集中返回跳过10条记录之后的前5条记录
Table result3 = in.orderBy($("a").asc()).offset(10).fetch(5);

Gruppenfenster

Die Gruppenfensteraggregation unterteilt Zeilen basierend auf Zeit- oder Zeilenanzahlintervallen in endliche Gruppen und führt für jede Gruppierung eine Aggregatfunktionsberechnung durch. Für Stapeltabellen sind Fenster eine praktische Möglichkeit, Datensätze nach Zeitintervallen zu gruppieren.

Grundlegende Anwendungsbeispiele sind wie folgt:

Table table = input
  .window([GroupWindow w].as("w"))  // 定义窗口并指定别名为 w
  .groupBy($("w"))  // 以窗口 w 对表进行分组
  .select($("b").sum());  // 聚合

In einer Streaming-Umgebung können Fensteraggregationen nur dann parallel berechnet werden, wenn sie zusätzlich zum Fenster nach einer oder mehreren Eigenschaften gruppiert werden

Table table = input
  .window([GroupWindow w].as("w"))  // 定义窗口并指定别名为 w
  .groupBy($("w"), $("a"))  // 以属性 a 和窗口 w 对表进行分组
  .select($("a"), $("b").sum());  // 聚合

Fenstereigenschaften wie der Anfang und das Ende eines Zeitfensters oder ein Zeilenzeitstempel können der Select-Klausel als Eigenschaften des Fensteralias hinzugefügt werden, z. B. w.start, w.end und w.rowtime. Die Fensterstart- und Zeilenzeitstempel umfassen die oberen und unteren Fenstergrenzen. Stattdessen ist der Zeitstempel des Fensterendes die einzige obere Fenstergrenze.

Beispielsweise hätte ein 30-minütiges rollierendes Fenster, das um 14:00 Uhr beginnt, „14:00:00.000“ als Startzeitstempel, „14:29:59.999“ als Zeilenzeitzeitstempel und „14:30:00.000“ als Endzeitstempel.

Table table = input
  .window([GroupWindow w].as("w"))  // 定义窗口并指定别名为 w
  .groupBy($("w"), $("a"))  // 以属性 a 和窗口 w 对表进行分组
  .select($("a"), $("w").start(), $("w").end(), $("w").rowtime(), $("b").count()); // 聚合并添加窗口开始、结束和 rowtime 时间戳

Die Table-API stellt eine Reihe vordefinierter Fensterklassen mit spezifischer Semantik bereit. Die unterstützten Fensterdefinitionen sind unten aufgeführt.

1.Tumble (taumelnde Fenster)

Rollierende Fenster weisen Reihen nicht überlappenden, zusammenhängenden Fenstern fester Länge zu.

Beispielsweise gruppiert ein gleitendes 5-Minuten-Fenster Zeilen in 5-Minuten-Intervallen. Das rollierende Fenster kann in Ereigniszeit, Verarbeitungszeit oder Anzahl der Zeilen definiert werden.

Methode beschreiben
über Definieren Sie die Länge des Fensters als Zeit- oder Zeilenzählintervall.
An Das Zeitattribut zum Gruppieren (Zeitintervall) oder Sortieren (Zeilenanzahl) der Daten. Batch-Abfragen unterstützen alle Eigenschaften vom Typ Long oder Timestamp. Stream-Verarbeitungsabfragen unterstützen nur deklarierte Ereigniszeit- oder Verarbeitungszeitattribute.
als Gibt den Alias ​​für das Fenster an. Der Alias ​​wird verwendet, um in einer groupBy()-Klausel auf ein Fenster zu verweisen und ermöglicht die Auswahl von Fenstereigenschaften wie Fensteranfang, -ende oder Zeilenzeitstempel in einer select()-Klausel.
// Tumbling Event-time Window
.window(Tumble.over(lit(10).minutes()).on($("rowtime")).as("w"));

// Tumbling Processing-time Window (assuming a processing-time attribute "proctime")
.window(Tumble.over(lit(10).minutes()).on($("proctime")).as("w"));

// Tumbling Row-count Window (assuming a processing-time attribute "proctime")
.window(Tumble.over(rowInterval(10)).on($("proctime")).as("w"));

2.Slide (Schiebefenster)

Schiebefenster haben eine feste Größe und werden in einem bestimmten Schiebeintervall verschoben. Wenn das Schiebeintervall kleiner als die Fenstergröße ist, überlappen sich die Schiebefenster. Daher können Zeilen mehreren Fenstern zugewiesen werden.

Beispielsweise weist ein gleitendes Fenster mit einer Größe von 15 Minuten und einem gleitenden Intervall von 5 Minuten jede Zeile drei verschiedenen Fenstern mit einer Größe von 15 Minuten zu und führt Berechnungen in 5-Minuten-Intervallen durch. Schiebefenster können in der Ereigniszeit, der Verarbeitungszeit oder der Anzahl der Zeilen definiert werden.

Methode beschreiben
über Definieren Sie die Länge des Fensters als Zeit- oder Zeilenzählintervall.
jeden Definieren Sie die Länge des Fensters als Zeit- oder Zeilenzählintervall. Das Gleitintervall muss vom gleichen Typ sein wie die Fensterlänge.
An Das Zeitattribut zum Gruppieren (Zeitintervall) oder Sortieren (Zeilenanzahl) der Daten. Batch-Abfragen unterstützen alle Eigenschaften vom Typ Long oder Timestamp. Stream-Verarbeitungsabfragen unterstützen nur deklarierte Ereigniszeit- oder Verarbeitungszeitattribute.
als Gibt den Alias ​​für das Fenster an. Der Alias ​​wird verwendet, um in einer groupBy()-Klausel auf ein Fenster zu verweisen und ermöglicht die Auswahl von Fenstereigenschaften wie Fensteranfang, -ende oder Zeilenzeitstempel in einer select()-Klausel.
// Sliding Event-time Window
.window(Slide.over(lit(10).minutes())
            .every(lit(5).minutes())
            .on($("rowtime"))
            .as("w"));

// Sliding Processing-time window (assuming a processing-time attribute "proctime")
.window(Slide.over(lit(10).minutes())
            .every(lit(5).minutes())
            .on($("proctime"))
            .as("w"));

// Sliding Row-count window (assuming a processing-time attribute "proctime")
.window(Slide.over(rowInterval(10)).every(rowInterval(5)).on($("proctime")).as("w"));

3. Sitzung (Sitzungsfenster)

Das Sitzungsfenster hat keine feste Größe, seine Grenzen werden durch Inaktivitätsintervalle definiert, d. h. wenn innerhalb des definierten Intervalls keine Ereignisse auftreten, wird das Sitzungsfenster geschlossen.

Wenn Sie beispielsweise ein Sitzungsfenster mit einem Intervall von 30 Minuten definieren und beobachten, dass eine Zeile 30 Minuten lang inaktiv ist (andernfalls würde die Zeile dem vorhandenen Fenster hinzugefügt werden) und 30 Minuten lang keine neuen Zeilen hinzugefügt werden, ist das Fenster aktiv geschlossen. Sitzungsfenster unterstützen Ereigniszeit und Verarbeitungszeit.

Methode beschreiben
mitGap Definieren Sie die Lücke zwischen den beiden Fenstern als Zeitintervall.
An Das Zeitattribut zum Gruppieren (Zeitintervall) oder Sortieren (Zeilenanzahl) der Daten. Batch-Abfragen unterstützen alle Lang- und Zeitstempel
als Gibt den Alias ​​für das Fenster an. Der Alias ​​wird verwendet, um in einer groupBy()-Klausel auf ein Fenster zu verweisen und ermöglicht die Auswahl von Fenstereigenschaften wie Fensteranfang, -ende oder Zeilenzeitstempel in einer select()-Klausel.
// Session Event-time Window
.window(Session.withGap(lit(10).minutes()).on($("rowtime")).as("w"));

// Session Processing-time Window (assuming a processing-time attribute "proctime")
.window(Session.withGap(lit(10).minutes()).on($("proctime")).as("w"));

Über Windows

OverWindow definiert den Zeilenbereich, über den die Aggregation berechnet wird

Table table = input
  .window([OverWindow w].as("w"))           // 用别名w定义over窗口
  .select($("a"), $("b").sum().over($("w")), $("c").min().over($("w"))); // 对窗口w进行聚合

Die Tabellen-API stellt die Over-Klasse zum Konfigurieren der Eigenschaften des Over-Fensters bereit. Überfenster können zur Ereigniszeit oder Verarbeitungszeit und innerhalb eines als Intervall oder Zeilenanzahl angegebenen Bereichs definiert werden

Partitionieren nach:

在一个或多个属性上定义输入的分区。每个分区单独排序,聚合函数分别应用于每个分区

在流环境中,如果窗口包含 partition by 子句,则只能并行计算 over window 聚合。如果没有 partitionBy(…),数据流将由单个非并行任务处理

Sortieren nach:

定义每个分区内行的顺序,从而定义聚合函数应用于行的顺序

对于流处理查询,必须声明事件时间或处理时间属性。目前,仅支持单个排序属性

Vorher:

定义了包含在窗口中并位于当前行之前的行的间隔。间隔可以是时间或行计数间隔

有界over window用间隔的大小指定,例如,时间间隔为10分钟或行计数间隔为10行

无界over window通过常量来指定,例如,用UNBOUNDED_RANGE指定时间间隔或用 UNBOUNDED_ROW 指定行计数间隔。无界 over windows 从分区的第一行开始

如果省略前面的子句,则使用 UNBOUNDED_RANGE 和 CURRENT_RANGE 作为窗口前后的默认值

Folgendes:

定义包含在窗口中并在当前行之后的行的窗口间隔。间隔必须以与前一个间隔(时间或行计数)相同的单位指定

目前,不支持在当前行之后有行的 over window。相反,可以指定两个常量之一:

CURRENT_ROW 将窗口的上限设置为当前行

CURRENT_RANGE 将窗口的上限设置为当前行的排序键,例如,与当前行具有相同排序键的所有行都包含在窗口中

如果省略后面的子句,则时间间隔窗口的上限定义为 CURRENT_RANGE,行计数间隔窗口的上限定义为CURRENT_ROW

Als:

为over window 指定别名。别名用于在之后的 select() 子句中引用该 over window

目前,同一个 select() 调用中的所有聚合函数必须在同一个 over window 上计算

Unbegrenzt über Windows

// 无界的事件时间 over window(假定有一个叫“rowtime”的事件时间属性)
.window(Over.partitionBy($("a")).orderBy($("rowtime")).preceding(UNBOUNDED_RANGE).as("w"));

// 无界的处理时间 over window(假定有一个叫“proctime”的处理时间属性)
.window(Over.partitionBy($("a")).orderBy("proctime").preceding(UNBOUNDED_RANGE).as("w"));

// 无界的事件时间行数 over window(假定有一个叫“rowtime”的事件时间属性)
.window(Over.partitionBy($("a")).orderBy($("rowtime")).preceding(UNBOUNDED_ROW).as("w"));
 
// 无界的处理时间行数 over window(假定有一个叫“proctime”的处理时间属性)
.window(Over.partitionBy($("a")).orderBy($("proctime")).preceding(UNBOUNDED_ROW).as("w"));

BoundedOver Windows

// 有界的事件时间 over window(假定有一个叫“rowtime”的事件时间属性)
.window(Over.partitionBy($("a")).orderBy($("rowtime")).preceding(lit(1).minutes()).as("w"));

// 有界的处理时间 over window(假定有一个叫“proctime”的处理时间属性)
.window(Over.partitionBy($("a")).orderBy($("proctime")).preceding(lit(1).minutes()).as("w"));

// 有界的事件时间行数 over window(假定有一个叫“rowtime”的事件时间属性)
.window(Over.partitionBy($("a")).orderBy($("rowtime")).preceding(rowInterval(10)).as("w"));
 
// 有界的处理时间行数 over window(假定有一个叫“proctime”的处理时间属性)
.window(Over.partitionBy($("a")).orderBy($("proctime")).preceding(rowInterval(10)).as("w"));

Guess you like

Origin blog.csdn.net/qq_38628046/article/details/109607935