概念与通用 API

The Table API and SQL are integrated in a joint API. The central concept of this API is a Table which serves as input and output of queries. This document shows the common structure of programs with Table API and SQL queries, how to register a Table, how to query a Table, and how to emit a Table.

Main Differences Between the Two Planners

  • Blink treats batch jobs as a special case of streaming. As such, the conversion between Table and DataSet is not supported, and batch jobs will not be translated into DateSet programs but translated into DataStream programs, the same as the streaming jobs.
  • The Blink planner does not support BatchTableSource, uses bounded StreamTableSource instead of it.
  • The Blink planner only support the brand new Catalog and does not support ExternalCatalog which is deprecated.
  • The implementations of FilterableTableSource for the old planner and the Blink planner are incompatible. The old planner will push down PlannerExpressions into FilterableTableSource, while the Blink planner will push down Expressions.
  • String based key-value config options (Please see the documentation about Configuration for details) are only used for the Blink planner.
  • The implementation(CalciteConfig) of PlannerConfig in two planners is different.
  • The Blink planner will optimize multiple-sinks into one DAG (supported only on TableEnvironment, not on StreamTableEnvironment). The old planner will always optimize each sink into a new DAG, where all DAGs are independent of each other.
  • The old planner does not support catalog statistics now, while the Blink planner does.

Structure of Table API and SQL Programs

All Table API and SQL programs for batch and streaming follow the same pattern. The following code example shows the common structure of Table API and SQL programs.

  1. // create a TableEnvironment for specific planner batch or streaming
  2. TableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // register a Table
  4. tableEnv.registerTable("table1", ...) // or
  5. tableEnv.registerTableSource("table2", ...); // or
  6. tableEnv.registerExternalCatalog("extCat", ...);
  7. // register an output Table
  8. tableEnv.registerTableSink("outputTable", ...);
  9. // create a Table from a Table API query
  10. Table tapiResult = tableEnv.scan("table1").select(...);
  11. // create a Table from a SQL query
  12. Table sqlResult = tableEnv.sqlQuery("SELECT ... FROM table2 ... ");
  13. // emit a Table API result Table to a TableSink, same for SQL result
  14. tapiResult.insertInto("outputTable");
  15. // execute
  16. tableEnv.execute("java_job");
  1. // create a TableEnvironment for specific planner batch or streaming
  2. val tableEnv = ... // see "Create a TableEnvironment" section
  3. // register a Table
  4. tableEnv.registerTable("table1", ...) // or
  5. tableEnv.registerTableSource("table2", ...) // or
  6. tableEnv.registerExternalCatalog("extCat", ...)
  7. // register an output Table
  8. tableEnv.registerTableSink("outputTable", ...);
  9. // create a Table from a Table API query
  10. val tapiResult = tableEnv.scan("table1").select(...)
  11. // create a Table from a SQL query
  12. val sqlResult = tableEnv.sqlQuery("SELECT ... FROM table2 ...")
  13. // emit a Table API result Table to a TableSink, same for SQL result
  14. tapiResult.insertInto("outputTable")
  15. // execute
  16. tableEnv.execute("scala_job")
  1. # create a TableEnvironment for specific planner batch or streaming
  2. table_env = ... # see "Create a TableEnvironment" section
  3. # register a Table
  4. table_env.register_table("table1", ...) # or
  5. table_env.register_table_source("table2", ...)
  6. # register an output Table
  7. table_env.register_table_sink("outputTable", ...);
  8. # create a Table from a Table API query
  9. tapi_result = table_env.scan("table1").select(...)
  10. # create a Table from a SQL query
  11. sql_result = table_env.sql_query("SELECT ... FROM table2 ...")
  12. # emit a Table API result Table to a TableSink, same for SQL result
  13. tapi_result.insert_into("outputTable")
  14. # execute
  15. table_env.execute("python_job")

Note: Table API and SQL queries can be easily integrated with and embedded into DataStream or DataSet programs. Have a look at the Integration with DataStream and DataSet API section to learn how DataStreams and DataSets can be converted into Tables and vice versa.

Create a TableEnvironment

The TableEnvironment is a central concept of the Table API and SQL integration. It is responsible for:

  • Registering a Table in the internal catalog
  • Registering an external catalog
  • Executing SQL queries
  • Registering a user-defined (scalar, table, or aggregation) function
  • Converting a DataStream or DataSet into a Table
  • Holding a reference to an ExecutionEnvironment or StreamExecutionEnvironment

A Table is always bound to a specific TableEnvironment. It is not possible to combine tables of different TableEnvironments in the same query, e.g., to join or union them.

A TableEnvironment is created by calling the static BatchTableEnvironment.create() or StreamTableEnvironment.create() method with a StreamExecutionEnvironment or an ExecutionEnvironment and an optional TableConfig. The TableConfig can be used to configure the TableEnvironment or to customize the query optimization and translation process (see Query Optimization).

Make sure to choose the specific planner BatchTableEnvironment/StreamTableEnvironment that matches your programming language.

If both planner jars are on the classpath (the default behavior), you should explicitly set which planner to use in the current program.

  1. // **********************
  2. // FLINK STREAMING QUERY
  3. // **********************
  4. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  5. import org.apache.flink.table.api.EnvironmentSettings;
  6. import org.apache.flink.table.api.java.StreamTableEnvironment;
  7. EnvironmentSettings fsSettings = EnvironmentSettings.newInstance().useOldPlanner().inStreamingMode().build();
  8. StreamExecutionEnvironment fsEnv = StreamExecutionEnvironment.getExecutionEnvironment();
  9. StreamTableEnvironment fsTableEnv = StreamTableEnvironment.create(fsEnv, fsSettings);
  10. // or TableEnvironment fsTableEnv = TableEnvironment.create(fsSettings);
  11. // ******************
  12. // FLINK BATCH QUERY
  13. // ******************
  14. import org.apache.flink.api.java.ExecutionEnvironment;
  15. import org.apache.flink.table.api.java.BatchTableEnvironment;
  16. ExecutionEnvironment fbEnv = ExecutionEnvironment.getExecutionEnvironment();
  17. BatchTableEnvironment fbTableEnv = BatchTableEnvironment.create(fbEnv);
  18. // **********************
  19. // BLINK STREAMING QUERY
  20. // **********************
  21. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  22. import org.apache.flink.table.api.EnvironmentSettings;
  23. import org.apache.flink.table.api.java.StreamTableEnvironment;
  24. StreamExecutionEnvironment bsEnv = StreamExecutionEnvironment.getExecutionEnvironment();
  25. EnvironmentSettings bsSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build();
  26. StreamTableEnvironment bsTableEnv = StreamTableEnvironment.create(bsEnv, bsSettings);
  27. // or TableEnvironment bsTableEnv = TableEnvironment.create(bsSettings);
  28. // ******************
  29. // BLINK BATCH QUERY
  30. // ******************
  31. import org.apache.flink.table.api.EnvironmentSettings;
  32. import org.apache.flink.table.api.TableEnvironment;
  33. EnvironmentSettings bbSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inBatchMode().build();
  34. TableEnvironment bbTableEnv = TableEnvironment.create(bbSettings);
  1. // **********************
  2. // FLINK STREAMING QUERY
  3. // **********************
  4. import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
  5. import org.apache.flink.table.api.EnvironmentSettings
  6. import org.apache.flink.table.api.scala.StreamTableEnvironment
  7. val fsSettings = EnvironmentSettings.newInstance().useOldPlanner().inStreamingMode().build()
  8. val fsEnv = StreamExecutionEnvironment.getExecutionEnvironment
  9. val fsTableEnv = StreamTableEnvironment.create(fsEnv, fsSettings)
  10. // or val fsTableEnv = TableEnvironment.create(fsSettings)
  11. // ******************
  12. // FLINK BATCH QUERY
  13. // ******************
  14. import org.apache.flink.api.scala.ExecutionEnvironment
  15. import org.apache.flink.table.api.scala.BatchTableEnvironment
  16. val fbEnv = ExecutionEnvironment.getExecutionEnvironment
  17. val fbTableEnv = BatchTableEnvironment.create(fbEnv)
  18. // **********************
  19. // BLINK STREAMING QUERY
  20. // **********************
  21. import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
  22. import org.apache.flink.table.api.EnvironmentSettings
  23. import org.apache.flink.table.api.scala.StreamTableEnvironment
  24. val bsEnv = StreamExecutionEnvironment.getExecutionEnvironment
  25. val bsSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build()
  26. val bsTableEnv = StreamTableEnvironment.create(bsEnv, bsSettings)
  27. // or val bsTableEnv = TableEnvironment.create(bsSettings)
  28. // ******************
  29. // BLINK BATCH QUERY
  30. // ******************
  31. import org.apache.flink.table.api.{EnvironmentSettings, TableEnvironment}
  32. val bbSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inBatchMode().build()
  33. val bbTableEnv = TableEnvironment.create(bbSettings)
  1. # **********************
  2. # FLINK STREAMING QUERY
  3. # **********************
  4. from pyflink.datastream import StreamExecutionEnvironment
  5. from pyflink.table import StreamTableEnvironment, EnvironmentSettings
  6. f_s_env = StreamExecutionEnvironment.get_execution_environment()
  7. f_s_settings = EnvironmentSettings.new_instance().use_old_planner().in_streaming_mode().build()
  8. f_s_t_env = StreamTableEnvironment.create(f_s_env, environment_settings=f_s_settings)
  9. # ******************
  10. # FLINK BATCH QUERY
  11. # ******************
  12. from pyflink.dataset import ExecutionEnvironment
  13. from pyflink.table import BatchTableEnvironment
  14. f_b_env = ExecutionEnvironment.get_execution_environment()
  15. f_b_t_env = BatchTableEnvironment.create(f_b_env, table_config)
  16. # **********************
  17. # BLINK STREAMING QUERY
  18. # **********************
  19. from pyflink.datastream import StreamExecutionEnvironment
  20. from pyflink.table import StreamTableEnvironment, EnvironmentSettings
  21. b_s_env = StreamExecutionEnvironment.get_execution_environment()
  22. b_s_settings = EnvironmentSettings.new_instance().use_blink_planner().in_streaming_mode().build()
  23. b_s_t_env = StreamTableEnvironment.create(b_s_env, environment_settings=b_s_settings)
  24. # ******************
  25. # BLINK BATCH QUERY
  26. # ******************
  27. from pyflink.table import EnvironmentSettings, BatchTableEnvironment
  28. b_b_settings = EnvironmentSettings.new_instance().use_blink_planner().in_batch_mode().build()
  29. b_b_t_env = BatchTableEnvironment.create(environment_settings=b_b_settings)

Note: If there is only one planner jar in /lib directory, you can use useAnyPlanner (use_any_planner for python) to create specific EnvironmentSettings.

Register Tables in the Catalog

A TableEnvironment maintains a catalog of tables which are registered by name. There are two types of tables, input tables and output tables. Input tables can be referenced in Table API and SQL queries and provide input data. Output tables can be used to emit the result of a Table API or SQL query to an external system.

An input table can be registered from various sources:

  • an existing Table object, usually the result of a Table API or SQL query.
  • a TableSource, which accesses external data, such as a file, database, or messaging system.
  • a DataStream or DataSet from a DataStream (only for stream job) or DataSet (only for batch job translated from old planner) program. Registering a DataStream or DataSet is discussed in the Integration with DataStream and DataSet API section.

An output table can be registered using a TableSink.

Register a Table

A Table is registered in a TableEnvironment as follows:

  1. // get a TableEnvironment
  2. TableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // table is the result of a simple projection query
  4. Table projTable = tableEnv.scan("X").select(...);
  5. // register the Table projTable as table "projectedTable"
  6. tableEnv.registerTable("projectedTable", projTable);
  1. // get a TableEnvironment
  2. val tableEnv = ... // see "Create a TableEnvironment" section
  3. // table is the result of a simple projection query
  4. val projTable: Table = tableEnv.scan("X").select(...)
  5. // register the Table projTable as table "projectedTable"
  6. tableEnv.registerTable("projectedTable", projTable)
  1. # get a TableEnvironment
  2. table_env = ... # see "Create a TableEnvironment" section
  3. # table is the result of a simple projection query
  4. proj_table = table_env.scan("X").select(...)
  5. # register the Table projTable as table "projectedTable"
  6. table_env.register_table("projectedTable", proj_table)

Note: A registered Table is treated similarly to a VIEW as known from relational database systems, i.e., the query that defines the Table is not optimized but will be inlined when another query references the registered Table. If multiple queries reference the same registered Table, it will be inlined for each referencing query and executed multiple times, i.e., the result of the registered Table will not be shared.

Register a TableSource

A TableSource provides access to external data which is stored in a storage system such as a database (MySQL, HBase, …), a file with a specific encoding (CSV, Apache [Parquet, Avro, ORC], …), or a messaging system (Apache Kafka, RabbitMQ, …).

Flink aims to provide TableSources for common data formats and storage systems. Please have a look at the Table Sources and Sinks page for a list of supported TableSources and instructions for how to build a custom TableSource.

A TableSource is registered in a TableEnvironment as follows:

  1. // get a TableEnvironment
  2. TableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // create a TableSource
  4. TableSource csvSource = new CsvTableSource("/path/to/file", ...);
  5. // register the TableSource as table "CsvTable"
  6. tableEnv.registerTableSource("CsvTable", csvSource);
  1. // get a TableEnvironment
  2. val tableEnv = ... // see "Create a TableEnvironment" section
  3. // create a TableSource
  4. val csvSource: TableSource = new CsvTableSource("/path/to/file", ...)
  5. // register the TableSource as table "CsvTable"
  6. tableEnv.registerTableSource("CsvTable", csvSource)
  1. # get a TableEnvironment
  2. table_env = ... # see "Create a TableEnvironment" section
  3. # create a TableSource
  4. csv_source = CsvTableSource("/path/to/file", ...)
  5. # register the TableSource as table "csvTable"
  6. table_env.register_table_source("csvTable", csv_source)

Note: A TableEnvironment used for Blink planner only accepts StreamTableSource, LookupableTableSource and InputFormatTableSource, and a StreamTableSource used for Blink planner on batch must be bounded.

Register a TableSink

A registered TableSink can be used to emit the result of a Table API or SQL query to an external storage system, such as a database, key-value store, message queue, or file system (in different encodings, e.g., CSV, Apache [Parquet, Avro, ORC], …).

Flink aims to provide TableSinks for common data formats and storage systems. Please see the documentation about Table Sources and Sinks page for details about available sinks and instructions for how to implement a custom TableSink.

A TableSink is registered in a TableEnvironment as follows:

  1. // get a TableEnvironment
  2. TableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // create a TableSink
  4. TableSink csvSink = new CsvTableSink("/path/to/file", ...);
  5. // define the field names and types
  6. String[] fieldNames = {"a", "b", "c"};
  7. TypeInformation[] fieldTypes = {Types.INT, Types.STRING, Types.LONG};
  8. // register the TableSink as table "CsvSinkTable"
  9. tableEnv.registerTableSink("CsvSinkTable", fieldNames, fieldTypes, csvSink);
  1. // get a TableEnvironment
  2. val tableEnv = ... // see "Create a TableEnvironment" section
  3. // create a TableSink
  4. val csvSink: TableSink = new CsvTableSink("/path/to/file", ...)
  5. // define the field names and types
  6. val fieldNames: Array[String] = Array("a", "b", "c")
  7. val fieldTypes: Array[TypeInformation[_]] = Array(Types.INT, Types.STRING, Types.LONG)
  8. // register the TableSink as table "CsvSinkTable"
  9. tableEnv.registerTableSink("CsvSinkTable", fieldNames, fieldTypes, csvSink)
  1. # get a TableEnvironment
  2. table_env = ... # see "Create a TableEnvironment" section
  3. # define the field names and types
  4. field_names = ["a", "b", "c"]
  5. field_types = [DataTypes.INT(), DataTypes.STRING(), DataTypes.BIGINT()]
  6. # create a TableSink
  7. csv_sink = CsvTableSink(field_names, field_types, "/path/to/file", ...)
  8. # register the TableSink as table "CsvSinkTable"
  9. table_env.register_table_sink("CsvSinkTable", csv_sink)

Register an External Catalog

An external catalog can provide information about external databases and tables such as their name, schema, statistics, and information for how to access data stored in an external database, table, or file.

An external catalog can be created by implementing the ExternalCatalog interface and is registered in a TableEnvironment as follows:

  1. // get a TableEnvironment
  2. TableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // create an external catalog
  4. ExternalCatalog catalog = new InMemoryExternalCatalog();
  5. // register the ExternalCatalog catalog
  6. tableEnv.registerExternalCatalog("InMemCatalog", catalog);
  1. // get a TableEnvironment
  2. val tableEnv = ... // see "Create a TableEnvironment" section
  3. // create an external catalog
  4. val catalog: ExternalCatalog = new InMemoryExternalCatalog
  5. // register the ExternalCatalog catalog
  6. tableEnv.registerExternalCatalog("InMemCatalog", catalog)

Once registered in a TableEnvironment, all tables defined in a ExternalCatalog can be accessed from Table API or SQL queries by specifying their full path, such as catalog.database.table.

Currently, Flink provides an InMemoryExternalCatalog for demo and testing purposes. However, the ExternalCatalog interface can also be used to connect catalogs like HCatalog or Metastore to the Table API.

Note: The Blink planner does not support external catalog.

Query a Table

Table API

The Table API is a language-integrated query API for Scala and Java. In contrast to SQL, queries are not specified as Strings but are composed step-by-step in the host language.

The API is based on the Table class which represents a table (streaming or batch) and offers methods to apply relational operations. These methods return a new Table object, which represents the result of applying the relational operation on the input Table. Some relational operations are composed of multiple method calls such as table.groupBy(…).select(), where groupBy(…) specifies a grouping of table, and select(…) the projection on the grouping of table.

The Table API document describes all Table API operations that are supported on streaming and batch tables.

The following example shows a simple Table API aggregation query:

  1. // get a TableEnvironment
  2. TableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // register Orders table
  4. // scan registered Orders table
  5. Table orders = tableEnv.scan("Orders");
  6. // compute revenue for all customers from France
  7. Table revenue = orders
  8. .filter("cCountry === 'FRANCE'")
  9. .groupBy("cID, cName")
  10. .select("cID, cName, revenue.sum AS revSum");
  11. // emit or convert Table
  12. // execute query
  1. // get a TableEnvironment
  2. val tableEnv = ... // see "Create a TableEnvironment" section
  3. // register Orders table
  4. // scan registered Orders table
  5. val orders = tableEnv.scan("Orders")
  6. // compute revenue for all customers from France
  7. val revenue = orders
  8. .filter('cCountry === "FRANCE")
  9. .groupBy('cID, 'cName)
  10. .select('cID, 'cName, 'revenue.sum AS 'revSum)
  11. // emit or convert Table
  12. // execute query

Note: The Scala Table API uses Scala Symbols, which start with a single tick (') to reference the attributes of a Table. The Table API uses Scala implicits. Make sure to import org.apache.flink.api.scala. and org.apache.flink.table.api.scala. in order to use Scala implicit conversions.

  1. # get a TableEnvironment
  2. table_env = # see "Create a TableEnvironment" section
  3. # register Orders table
  4. # scan registered Orders table
  5. orders = table_env.scan("Orders")
  6. # compute revenue for all customers from France
  7. revenue = orders \
  8. .filter("cCountry === 'FRANCE'") \
  9. .group_by("cID, cName") \
  10. .select("cID, cName, revenue.sum AS revSum")
  11. # emit or convert Table
  12. # execute query

SQL

Flink’s SQL integration is based on Apache Calcite, which implements the SQL standard. SQL queries are specified as regular Strings.

The SQL document describes Flink’s SQL support for streaming and batch tables.

The following example shows how to specify a query and return the result as a Table.

  1. // get a TableEnvironment
  2. TableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // register Orders table
  4. // compute revenue for all customers from France
  5. Table revenue = tableEnv.sqlQuery(
  6. "SELECT cID, cName, SUM(revenue) AS revSum " +
  7. "FROM Orders " +
  8. "WHERE cCountry = 'FRANCE' " +
  9. "GROUP BY cID, cName"
  10. );
  11. // emit or convert Table
  12. // execute query
  1. // get a TableEnvironment
  2. val tableEnv = ... // see "Create a TableEnvironment" section
  3. // register Orders table
  4. // compute revenue for all customers from France
  5. val revenue = tableEnv.sqlQuery("""
  6. |SELECT cID, cName, SUM(revenue) AS revSum
  7. |FROM Orders
  8. |WHERE cCountry = 'FRANCE'
  9. |GROUP BY cID, cName
  10. """.stripMargin)
  11. // emit or convert Table
  12. // execute query
  1. # get a TableEnvironment
  2. table_env = ... # see "Create a TableEnvironment" section
  3. # register Orders table
  4. # compute revenue for all customers from France
  5. revenue = table_env.sql_query(
  6. "SELECT cID, cName, SUM(revenue) AS revSum "
  7. "FROM Orders "
  8. "WHERE cCountry = 'FRANCE' "
  9. "GROUP BY cID, cName"
  10. )
  11. # emit or convert Table
  12. # execute query

The following example shows how to specify an update query that inserts its result into a registered table.

  1. // get a TableEnvironment
  2. TableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // register "Orders" table
  4. // register "RevenueFrance" output table
  5. // compute revenue for all customers from France and emit to "RevenueFrance"
  6. tableEnv.sqlUpdate(
  7. "INSERT INTO RevenueFrance " +
  8. "SELECT cID, cName, SUM(revenue) AS revSum " +
  9. "FROM Orders " +
  10. "WHERE cCountry = 'FRANCE' " +
  11. "GROUP BY cID, cName"
  12. );
  13. // execute query
  1. // get a TableEnvironment
  2. val tableEnv = ... // see "Create a TableEnvironment" section
  3. // register "Orders" table
  4. // register "RevenueFrance" output table
  5. // compute revenue for all customers from France and emit to "RevenueFrance"
  6. tableEnv.sqlUpdate("""
  7. |INSERT INTO RevenueFrance
  8. |SELECT cID, cName, SUM(revenue) AS revSum
  9. |FROM Orders
  10. |WHERE cCountry = 'FRANCE'
  11. |GROUP BY cID, cName
  12. """.stripMargin)
  13. // execute query
  1. # get a TableEnvironment
  2. table_env = ... # see "Create a TableEnvironment" section
  3. # register "Orders" table
  4. # register "RevenueFrance" output table
  5. # compute revenue for all customers from France and emit to "RevenueFrance"
  6. table_env.sql_update(
  7. "INSERT INTO RevenueFrance "
  8. "SELECT cID, cName, SUM(revenue) AS revSum "
  9. "FROM Orders "
  10. "WHERE cCountry = 'FRANCE' "
  11. "GROUP BY cID, cName"
  12. )
  13. # execute query

Mixing Table API and SQL

Table API and SQL queries can be easily mixed because both return Table objects:

  • A Table API query can be defined on the Table object returned by a SQL query.
  • A SQL query can be defined on the result of a Table API query by registering the resulting Table in the TableEnvironment and referencing it in the FROM clause of the SQL query.

Emit a Table

A Table is emitted by writing it to a TableSink. A TableSink is a generic interface to support a wide variety of file formats (e.g. CSV, Apache Parquet, Apache Avro), storage systems (e.g., JDBC, Apache HBase, Apache Cassandra, Elasticsearch), or messaging systems (e.g., Apache Kafka, RabbitMQ).

A batch Table can only be written to a BatchTableSink, while a streaming Table requires either an AppendStreamTableSink, a RetractStreamTableSink, or an UpsertStreamTableSink.

Please see the documentation about Table Sources & Sinks for details about available sinks and instructions for how to implement a custom TableSink.

The Table.insertInto(String tableName) method emits the Table to a registered TableSink. The method looks up the TableSink from the catalog by the name and validates that the schema of the Table is identical to the schema of the TableSink.

The following examples shows how to emit a Table:

  1. // get a TableEnvironment
  2. TableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // create a TableSink
  4. TableSink sink = new CsvTableSink("/path/to/file", fieldDelim = "|");
  5. // register the TableSink with a specific schema
  6. String[] fieldNames = {"a", "b", "c"};
  7. TypeInformation[] fieldTypes = {Types.INT, Types.STRING, Types.LONG};
  8. tableEnv.registerTableSink("CsvSinkTable", fieldNames, fieldTypes, sink);
  9. // compute a result Table using Table API operators and/or SQL queries
  10. Table result = ...
  11. // emit the result Table to the registered TableSink
  12. result.insertInto("CsvSinkTable");
  13. // execute the program
  1. // get a TableEnvironment
  2. val tableEnv = ... // see "Create a TableEnvironment" section
  3. // create a TableSink
  4. val sink: TableSink = new CsvTableSink("/path/to/file", fieldDelim = "|")
  5. // register the TableSink with a specific schema
  6. val fieldNames: Array[String] = Array("a", "b", "c")
  7. val fieldTypes: Array[TypeInformation] = Array(Types.INT, Types.STRING, Types.LONG)
  8. tableEnv.registerTableSink("CsvSinkTable", fieldNames, fieldTypes, sink)
  9. // compute a result Table using Table API operators and/or SQL queries
  10. val result: Table = ...
  11. // emit the result Table to the registered TableSink
  12. result.insertInto("CsvSinkTable")
  13. // execute the program
  1. # get a TableEnvironment
  2. table_env = ... # see "Create a TableEnvironment" section
  3. field_names = ["a", "b", "c"]
  4. field_types = [DataTypes.INT(), DataTypes.STRING(), DataTypes.BIGINT()]
  5. # create a TableSink
  6. sink = CsvTableSink(field_names, field_types, "/path/to/file", "|")
  7. table_env.register_table_sink("CsvSinkTable", sink)
  8. # compute a result Table using Table API operators and/or SQL queries
  9. result = ...
  10. # emit the result Table to the registered TableSink
  11. result.insert_into("CsvSinkTable")
  12. # execute the program

Translate and Execute a Query

The behavior of translating and executing a query is different for the two planners.

Table API and SQL queries are translated into DataStream or DataSet programs depending on whether their input is a streaming or batch input. A query is internally represented as a logical query plan and is translated in two phases:

  • Optimization of the logical plan
  • Translation into a DataStream or DataSet programA Table API or SQL query is translated when:
  • a Table is emitted to a TableSink, i.e., when Table.insertInto() is called.
  • a SQL update query is specified, i.e., when TableEnvironment.sqlUpdate() is called.
  • a Table is converted into a DataStream or DataSet (see Integration with DataStream and DataSet API).

Once translated, a Table API or SQL query is handled like a regular DataStream or DataSet program and is executed when StreamExecutionEnvironment.execute() or ExecutionEnvironment.execute() is called.

Table API and SQL queries are translated into DataStream programs whether their input is streaming or batch. A query is internally represented as a logical query plan and is translated in two phases:

  • Optimization of the logical plan,
  • Translation into a DataStream program.The behavior of translating a query is different for TableEnvironment and StreamTableEnvironment.

For TableEnvironment, A Table API or SQL query is translated when TableEnvironment.execute() is called, because TableEnvironment will optimize multiple-sinks into one DAG.

while for StreamTableEnvironment, A Table API or SQL query is translated when:

  • a Table is emitted to a TableSink, i.e., when Table.insertInto() is called.
  • a SQL update query is specified, i.e., when TableEnvironment.sqlUpdate() is called.
  • a Table is converted into a DataStream.

Once translated, a Table API or SQL query is handled like a regular DataStream program and is executed when TableEnvironment.execute() or StreamExecutionEnvironment.execute() is called.

Integration with DataStream and DataSet API

Both planners on stream can integrate with the DataStream API. Only old planner can integrate with the DataSet API, Blink planner on batch could not be combined with both.Note: The DataSet API discussed below is only relevant for the old planner on batch.

Table API and SQL queries can be easily integrated with and embedded into DataStream and DataSet programs. For instance, it is possible to query an external table (for example from a RDBMS), do some pre-processing, such as filtering, projecting, aggregating, or joining with meta data, and then further process the data with either the DataStream or DataSet API (and any of the libraries built on top of these APIs, such as CEP or Gelly). Inversely, a Table API or SQL query can also be applied on the result of a DataStream or DataSet program.

This interaction can be achieved by converting a DataStream or DataSet into a Table and vice versa. In this section, we describe how these conversions are done.

Implicit Conversion for Scala

The Scala Table API features implicit conversions for the DataSet, DataStream, and Table classes. These conversions are enabled by importing the package org.apache.flink.table.api.scala. in addition to org.apache.flink.api.scala. for the Scala DataStream API.

Register a DataStream or DataSet as Table

A DataStream or DataSet can be registered in a TableEnvironment as a Table. The schema of the resulting table depends on the data type of the registered DataStream or DataSet. Please check the section about mapping of data types to table schema for details.

  1. // get StreamTableEnvironment
  2. // registration of a DataSet in a BatchTableEnvironment is equivalent
  3. StreamTableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  4. DataStream<Tuple2<Long, String>> stream = ...
  5. // register the DataStream as Table "myTable" with fields "f0", "f1"
  6. tableEnv.registerDataStream("myTable", stream);
  7. // register the DataStream as table "myTable2" with fields "myLong", "myString"
  8. tableEnv.registerDataStream("myTable2", stream, "myLong, myString");
  1. // get TableEnvironment
  2. // registration of a DataSet is equivalent
  3. val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section
  4. val stream: DataStream[(Long, String)] = ...
  5. // register the DataStream as Table "myTable" with fields "f0", "f1"
  6. tableEnv.registerDataStream("myTable", stream)
  7. // register the DataStream as table "myTable2" with fields "myLong", "myString"
  8. tableEnv.registerDataStream("myTable2", stream, 'myLong, 'myString)

Note: The name of a DataStream Table must not match the ^DataStreamTable[0-9]+ pattern and the name of a DataSet Table must not match the ^DataSetTable[0-9]+ pattern. These patterns are reserved for internal use only.

Convert a DataStream or DataSet into a Table

Instead of registering a DataStream or DataSet in a TableEnvironment, it can also be directly converted into a Table. This is convenient if you want to use the Table in a Table API query.

  1. // get StreamTableEnvironment
  2. // registration of a DataSet in a BatchTableEnvironment is equivalent
  3. StreamTableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  4. DataStream<Tuple2<Long, String>> stream = ...
  5. // Convert the DataStream into a Table with default fields "f0", "f1"
  6. Table table1 = tableEnv.fromDataStream(stream);
  7. // Convert the DataStream into a Table with fields "myLong", "myString"
  8. Table table2 = tableEnv.fromDataStream(stream, "myLong, myString");
  1. // get TableEnvironment
  2. // registration of a DataSet is equivalent
  3. val tableEnv = ... // see "Create a TableEnvironment" section
  4. val stream: DataStream[(Long, String)] = ...
  5. // convert the DataStream into a Table with default fields '_1, '_2
  6. val table1: Table = tableEnv.fromDataStream(stream)
  7. // convert the DataStream into a Table with fields 'myLong, 'myString
  8. val table2: Table = tableEnv.fromDataStream(stream, 'myLong, 'myString)

Convert a Table into a DataStream or DataSet

A Table can be converted into a DataStream or DataSet. In this way, custom DataStream or DataSet programs can be run on the result of a Table API or SQL query.

When converting a Table into a DataStream or DataSet, you need to specify the data type of the resulting DataStream or DataSet, i.e., the data type into which the rows of the Table are to be converted. Often the most convenient conversion type is Row. The following list gives an overview of the features of the different options:

  • Row: fields are mapped by position, arbitrary number of fields, support for null values, no type-safe access.
  • POJO: fields are mapped by name (POJO fields must be named as Table fields), arbitrary number of fields, support for null values, type-safe access.
  • Case Class: fields are mapped by position, no support for null values, type-safe access.
  • Tuple: fields are mapped by position, limitation to 22 (Scala) or 25 (Java) fields, no support for null values, type-safe access.
  • Atomic Type: Table must have a single field, no support for null values, type-safe access.

Convert a Table into a DataStream

A Table that is the result of a streaming query will be updated dynamically, i.e., it is changing as new records arrive on the query’s input streams. Hence, the DataStream into which such a dynamic query is converted needs to encode the updates of the table.

There are two modes to convert a Table into a DataStream:

  • Append Mode: This mode can only be used if the dynamic Table is only modified by INSERT changes, i.e, it is append-only and previously emitted results are never updated.
  • Retract Mode: This mode can always be used. It encodes INSERT and DELETE changes with a boolean flag.
  1. // get StreamTableEnvironment.
  2. StreamTableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // Table with two fields (String name, Integer age)
  4. Table table = ...
  5. // convert the Table into an append DataStream of Row by specifying the class
  6. DataStream<Row> dsRow = tableEnv.toAppendStream(table, Row.class);
  7. // convert the Table into an append DataStream of Tuple2<String, Integer>
  8. // via a TypeInformation
  9. TupleTypeInfo<Tuple2<String, Integer>> tupleType = new TupleTypeInfo<>(
  10. Types.STRING(),
  11. Types.INT());
  12. DataStream<Tuple2<String, Integer>> dsTuple =
  13. tableEnv.toAppendStream(table, tupleType);
  14. // convert the Table into a retract DataStream of Row.
  15. // A retract stream of type X is a DataStream<Tuple2<Boolean, X>>.
  16. // The boolean field indicates the type of the change.
  17. // True is INSERT, false is DELETE.
  18. DataStream<Tuple2<Boolean, Row>> retractStream =
  19. tableEnv.toRetractStream(table, Row.class);
  1. // get TableEnvironment.
  2. // registration of a DataSet is equivalent
  3. val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section
  4. // Table with two fields (String name, Integer age)
  5. val table: Table = ...
  6. // convert the Table into an append DataStream of Row
  7. val dsRow: DataStream[Row] = tableEnv.toAppendStream[Row](table)
  8. // convert the Table into an append DataStream of Tuple2[String, Int]
  9. val dsTuple: DataStream[(String, Int)] dsTuple =
  10. tableEnv.toAppendStream[(String, Int)](table)
  11. // convert the Table into a retract DataStream of Row.
  12. // A retract stream of type X is a DataStream[(Boolean, X)].
  13. // The boolean field indicates the type of the change.
  14. // True is INSERT, false is DELETE.
  15. val retractStream: DataStream[(Boolean, Row)] = tableEnv.toRetractStream[Row](table)

Note: A detailed discussion about dynamic tables and their properties is given in the Dynamic Tables document.

Convert a Table into a DataSet

A Table is converted into a DataSet as follows:

  1. // get BatchTableEnvironment
  2. BatchTableEnvironment tableEnv = BatchTableEnvironment.create(env);
  3. // Table with two fields (String name, Integer age)
  4. Table table = ...
  5. // convert the Table into a DataSet of Row by specifying a class
  6. DataSet<Row> dsRow = tableEnv.toDataSet(table, Row.class);
  7. // convert the Table into a DataSet of Tuple2<String, Integer> via a TypeInformation
  8. TupleTypeInfo<Tuple2<String, Integer>> tupleType = new TupleTypeInfo<>(
  9. Types.STRING(),
  10. Types.INT());
  11. DataSet<Tuple2<String, Integer>> dsTuple =
  12. tableEnv.toDataSet(table, tupleType);
  1. // get TableEnvironment
  2. // registration of a DataSet is equivalent
  3. val tableEnv = BatchTableEnvironment.create(env)
  4. // Table with two fields (String name, Integer age)
  5. val table: Table = ...
  6. // convert the Table into a DataSet of Row
  7. val dsRow: DataSet[Row] = tableEnv.toDataSet[Row](table)
  8. // convert the Table into a DataSet of Tuple2[String, Int]
  9. val dsTuple: DataSet[(String, Int)] = tableEnv.toDataSet[(String, Int)](table)

Mapping of Data Types to Table Schema

Flink’s DataStream and DataSet APIs support very diverse types. Composite types such as Tuples (built-in Scala and Flink Java tuples), POJOs, Scala case classes, and Flink’s Row type allow for nested data structures with multiple fields that can be accessed in table expressions. Other types are treated as atomic types. In the following, we describe how the Table API converts these types into an internal row representation and show examples of converting a DataStream into a Table.

The mapping of a data type to a table schema can happen in two ways: based on the field positions or based on the field names.

Position-based Mapping

Position-based mapping can be used to give fields a more meaningful name while keeping the field order. This mapping is available for composite data types with a defined field order as well as atomic types. Composite data types such as tuples, rows, and case classes have such a field order. However, fields of a POJO must be mapped based on the field names (see next section). Fields can be projected out but can’t be renamed using an alias as.

When defining a position-based mapping, the specified names must not exist in the input data type, otherwise the API will assume that the mapping should happen based on the field names. If no field names are specified, the default field names and field order of the composite type are used or f0 for atomic types.

  1. // get a StreamTableEnvironment, works for BatchTableEnvironment equivalently
  2. StreamTableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section;
  3. DataStream<Tuple2<Long, Integer>> stream = ...
  4. // convert DataStream into Table with default field names "f0" and "f1"
  5. Table table = tableEnv.fromDataStream(stream);
  6. // convert DataStream into Table with field "myLong" only
  7. Table table = tableEnv.fromDataStream(stream, "myLong");
  8. // convert DataStream into Table with field names "myLong" and "myInt"
  9. Table table = tableEnv.fromDataStream(stream, "myLong, myInt");
  1. // get a TableEnvironment
  2. val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section
  3. val stream: DataStream[(Long, Int)] = ...
  4. // convert DataStream into Table with default field names "_1" and "_2"
  5. val table: Table = tableEnv.fromDataStream(stream)
  6. // convert DataStream into Table with field "myLong" only
  7. val table: Table = tableEnv.fromDataStream(stream, 'myLong)
  8. // convert DataStream into Table with field names "myLong" and "myInt"
  9. val table: Table = tableEnv.fromDataStream(stream, 'myLong, 'myInt)

Name-based Mapping

Name-based mapping can be used for any data type including POJOs. It is the most flexible way of defining a table schema mapping. All fields in the mapping are referenced by name and can be possibly renamed using an alias as. Fields can be reordered and projected out.

If no field names are specified, the default field names and field order of the composite type are used or f0 for atomic types.

  1. // get a StreamTableEnvironment, works for BatchTableEnvironment equivalently
  2. StreamTableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. DataStream<Tuple2<Long, Integer>> stream = ...
  4. // convert DataStream into Table with default field names "f0" and "f1"
  5. Table table = tableEnv.fromDataStream(stream);
  6. // convert DataStream into Table with field "f1" only
  7. Table table = tableEnv.fromDataStream(stream, "f1");
  8. // convert DataStream into Table with swapped fields
  9. Table table = tableEnv.fromDataStream(stream, "f1, f0");
  10. // convert DataStream into Table with swapped fields and field names "myInt" and "myLong"
  11. Table table = tableEnv.fromDataStream(stream, "f1 as myInt, f0 as myLong");
  1. // get a TableEnvironment
  2. val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section
  3. val stream: DataStream[(Long, Int)] = ...
  4. // convert DataStream into Table with default field names "_1" and "_2"
  5. val table: Table = tableEnv.fromDataStream(stream)
  6. // convert DataStream into Table with field "_2" only
  7. val table: Table = tableEnv.fromDataStream(stream, '_2)
  8. // convert DataStream into Table with swapped fields
  9. val table: Table = tableEnv.fromDataStream(stream, '_2, '_1)
  10. // convert DataStream into Table with swapped fields and field names "myInt" and "myLong"
  11. val table: Table = tableEnv.fromDataStream(stream, '_2 as 'myInt, '_1 as 'myLong)

Atomic Types

Flink treats primitives (Integer, Double, String) or generic types (types that cannot be analyzed and decomposed) as atomic types. A DataStream or DataSet of an atomic type is converted into a Table with a single attribute. The type of the attribute is inferred from the atomic type and the name of the attribute can be specified.

  1. // get a StreamTableEnvironment, works for BatchTableEnvironment equivalently
  2. StreamTableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. DataStream<Long> stream = ...
  4. // convert DataStream into Table with default field name "f0"
  5. Table table = tableEnv.fromDataStream(stream);
  6. // convert DataStream into Table with field name "myLong"
  7. Table table = tableEnv.fromDataStream(stream, "myLong");
  1. // get a TableEnvironment
  2. val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section
  3. val stream: DataStream[Long] = ...
  4. // convert DataStream into Table with default field name "f0"
  5. val table: Table = tableEnv.fromDataStream(stream)
  6. // convert DataStream into Table with field name "myLong"
  7. val table: Table = tableEnv.fromDataStream(stream, 'myLong)

Tuples (Scala and Java) and Case Classes (Scala only)

Flink supports Scala’s built-in tuples and provides its own tuple classes for Java. DataStreams and DataSets of both kinds of tuples can be converted into tables. Fields can be renamed by providing names for all fields (mapping based on position). If no field names are specified, the default field names are used. If the original field names (f0, f1, … for Flink Tuples and _1, _2, … for Scala Tuples) are referenced, the API assumes that the mapping is name-based instead of position-based. Name-based mapping allows for reordering fields and projection with alias (as).

  1. // get a StreamTableEnvironment, works for BatchTableEnvironment equivalently
  2. StreamTableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. DataStream<Tuple2<Long, String>> stream = ...
  4. // convert DataStream into Table with default field names "f0", "f1"
  5. Table table = tableEnv.fromDataStream(stream);
  6. // convert DataStream into Table with renamed field names "myLong", "myString" (position-based)
  7. Table table = tableEnv.fromDataStream(stream, "myLong, myString");
  8. // convert DataStream into Table with reordered fields "f1", "f0" (name-based)
  9. Table table = tableEnv.fromDataStream(stream, "f1, f0");
  10. // convert DataStream into Table with projected field "f1" (name-based)
  11. Table table = tableEnv.fromDataStream(stream, "f1");
  12. // convert DataStream into Table with reordered and aliased fields "myString", "myLong" (name-based)
  13. Table table = tableEnv.fromDataStream(stream, "f1 as 'myString', f0 as 'myLong'");
  1. // get a TableEnvironment
  2. val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section
  3. val stream: DataStream[(Long, String)] = ...
  4. // convert DataStream into Table with renamed default field names '_1, '_2
  5. val table: Table = tableEnv.fromDataStream(stream)
  6. // convert DataStream into Table with field names "myLong", "myString" (position-based)
  7. val table: Table = tableEnv.fromDataStream(stream, 'myLong, 'myString)
  8. // convert DataStream into Table with reordered fields "_2", "_1" (name-based)
  9. val table: Table = tableEnv.fromDataStream(stream, '_2, '_1)
  10. // convert DataStream into Table with projected field "_2" (name-based)
  11. val table: Table = tableEnv.fromDataStream(stream, '_2)
  12. // convert DataStream into Table with reordered and aliased fields "myString", "myLong" (name-based)
  13. val table: Table = tableEnv.fromDataStream(stream, '_2 as 'myString, '_1 as 'myLong)
  14. // define case class
  15. case class Person(name: String, age: Int)
  16. val streamCC: DataStream[Person] = ...
  17. // convert DataStream into Table with default field names 'name, 'age
  18. val table = tableEnv.fromDataStream(streamCC)
  19. // convert DataStream into Table with field names 'myName, 'myAge (position-based)
  20. val table = tableEnv.fromDataStream(streamCC, 'myName, 'myAge)
  21. // convert DataStream into Table with reordered and aliased fields "myAge", "myName" (name-based)
  22. val table: Table = tableEnv.fromDataStream(stream, 'age as 'myAge, 'name as 'myName)

POJO (Java and Scala)

Flink supports POJOs as composite types. The rules for what determines a POJO are documented here.

When converting a POJO DataStream or DataSet into a Table without specifying field names, the names of the original POJO fields are used. The name mapping requires the original names and cannot be done by positions. Fields can be renamed using an alias (with the as keyword), reordered, and projected.

  1. // get a StreamTableEnvironment, works for BatchTableEnvironment equivalently
  2. StreamTableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // Person is a POJO with fields "name" and "age"
  4. DataStream<Person> stream = ...
  5. // convert DataStream into Table with default field names "age", "name" (fields are ordered by name!)
  6. Table table = tableEnv.fromDataStream(stream);
  7. // convert DataStream into Table with renamed fields "myAge", "myName" (name-based)
  8. Table table = tableEnv.fromDataStream(stream, "age as myAge, name as myName");
  9. // convert DataStream into Table with projected field "name" (name-based)
  10. Table table = tableEnv.fromDataStream(stream, "name");
  11. // convert DataStream into Table with projected and renamed field "myName" (name-based)
  12. Table table = tableEnv.fromDataStream(stream, "name as myName");
  1. // get a TableEnvironment
  2. val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section
  3. // Person is a POJO with field names "name" and "age"
  4. val stream: DataStream[Person] = ...
  5. // convert DataStream into Table with default field names "age", "name" (fields are ordered by name!)
  6. val table: Table = tableEnv.fromDataStream(stream)
  7. // convert DataStream into Table with renamed fields "myAge", "myName" (name-based)
  8. val table: Table = tableEnv.fromDataStream(stream, 'age as 'myAge, 'name as 'myName)
  9. // convert DataStream into Table with projected field "name" (name-based)
  10. val table: Table = tableEnv.fromDataStream(stream, 'name)
  11. // convert DataStream into Table with projected and renamed field "myName" (name-based)
  12. val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName)

Row

The Row data type supports an arbitrary number of fields and fields with null values. Field names can be specified via a RowTypeInfo or when converting a Row DataStream or DataSet into a Table. The row type supports mapping of fields by position and by name. Fields can be renamed by providing names for all fields (mapping based on position) or selected individually for projection/ordering/renaming (mapping based on name).

  1. // get a StreamTableEnvironment, works for BatchTableEnvironment equivalently
  2. StreamTableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section
  3. // DataStream of Row with two fields "name" and "age" specified in `RowTypeInfo`
  4. DataStream<Row> stream = ...
  5. // convert DataStream into Table with default field names "name", "age"
  6. Table table = tableEnv.fromDataStream(stream);
  7. // convert DataStream into Table with renamed field names "myName", "myAge" (position-based)
  8. Table table = tableEnv.fromDataStream(stream, "myName, myAge");
  9. // convert DataStream into Table with renamed fields "myName", "myAge" (name-based)
  10. Table table = tableEnv.fromDataStream(stream, "name as myName, age as myAge");
  11. // convert DataStream into Table with projected field "name" (name-based)
  12. Table table = tableEnv.fromDataStream(stream, "name");
  13. // convert DataStream into Table with projected and renamed field "myName" (name-based)
  14. Table table = tableEnv.fromDataStream(stream, "name as myName");
  1. // get a TableEnvironment
  2. val tableEnv: StreamTableEnvironment = ... // see "Create a TableEnvironment" section
  3. // DataStream of Row with two fields "name" and "age" specified in `RowTypeInfo`
  4. val stream: DataStream[Row] = ...
  5. // convert DataStream into Table with default field names "name", "age"
  6. val table: Table = tableEnv.fromDataStream(stream)
  7. // convert DataStream into Table with renamed field names "myName", "myAge" (position-based)
  8. val table: Table = tableEnv.fromDataStream(stream, 'myName, 'myAge)
  9. // convert DataStream into Table with renamed fields "myName", "myAge" (name-based)
  10. val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName, 'age as 'myAge)
  11. // convert DataStream into Table with projected field "name" (name-based)
  12. val table: Table = tableEnv.fromDataStream(stream, 'name)
  13. // convert DataStream into Table with projected and renamed field "myName" (name-based)
  14. val table: Table = tableEnv.fromDataStream(stream, 'name as 'myName)

Query Optimization

Apache Flink leverages Apache Calcite to optimize and translate queries. The optimization currently performed include projection and filter push-down, subquery decorrelation, and other kinds of query rewriting. Old planner does not yet optimize the order of joins, but executes them in the same order as defined in the query (order of Tables in the FROM clause and/or order of join predicates in the WHERE clause).

It is possible to tweak the set of optimization rules which are applied in different phases by providing a CalciteConfig object. This can be created via a builder by calling CalciteConfig.createBuilder()) and is provided to the TableEnvironment by calling tableEnv.getConfig.setPlannerConfig(calciteConfig).

Apache Flink leverages and extends Apache Calcite to perform sophisticated query optimization.This includes a series of rule and cost-based optimizations such as:

  • 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

Note: IN/EXISTS/NOT IN/NOT EXISTS are currently only supported in conjunctive conditions in subquery rewriting.

The optimizer makes intelligent decisions, based not only on the plan but also rich statistics available from the data sources and fine-grain costs for each operator such as io, cpu, network, and memory.

Advanced users may provide custom optimizations via a CalciteConfig object that can be provided to the table environment by calling TableEnvironment#getConfig#setPlannerConfig.

Explaining a Table

The Table API provides a mechanism to explain the logical and optimized query plans to compute a Table. This is done through the TableEnvironment.explain(table) method or TableEnvironment.explain() method. explain(table) returns the plan of a given Table. explain() returns the result of a multiple-sinks plan and is mainly used for the Blink planner. It returns a String describing three plans:

  • the Abstract Syntax Tree of the relational query, i.e., the unoptimized logical query plan,
  • the optimized logical query plan, and
  • the physical execution plan.The following code shows an example and the corresponding output for given Table using explain(table):
  1. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  2. StreamTableEnvironment tEnv = StreamTableEnvironment.create(env);
  3. DataStream<Tuple2<Integer, String>> stream1 = env.fromElements(new Tuple2<>(1, "hello"));
  4. DataStream<Tuple2<Integer, String>> stream2 = env.fromElements(new Tuple2<>(1, "hello"));
  5. Table table1 = tEnv.fromDataStream(stream1, "count, word");
  6. Table table2 = tEnv.fromDataStream(stream2, "count, word");
  7. Table table = table1
  8. .where("LIKE(word, 'F%')")
  9. .unionAll(table2);
  10. String explanation = tEnv.explain(table);
  11. System.out.println(explanation);
  1. val env = StreamExecutionEnvironment.getExecutionEnvironment
  2. val tEnv = StreamTableEnvironment.create(env)
  3. val table1 = env.fromElements((1, "hello")).toTable(tEnv, 'count, 'word)
  4. val table2 = env.fromElements((1, "hello")).toTable(tEnv, 'count, 'word)
  5. val table = table1
  6. .where('word.like("F%"))
  7. .unionAll(table2)
  8. val explanation: String = tEnv.explain(table)
  9. println(explanation)
  1. env = StreamExecutionEnvironment.get_execution_environment()
  2. t_env = StreamTableEnvironment.create(env)
  3. table1 = t_env.from_elements([(1, "hello")], ["count", "word"])
  4. table2 = t_env.from_elements([(1, "hello")], ["count", "word"])
  5. table = table1 \
  6. .where("LIKE(word, 'F%')") \
  7. .union_all(table2)
  8. explanation = t_env.explain(table)
  9. print(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. == Optimized Logical Plan ==
  7. DataStreamUnion(all=[true], union all=[count, word])
  8. DataStreamCalc(select=[count, word], where=[LIKE(word, _UTF-16LE'F%')])
  9. DataStreamScan(id=[1], fields=[count, word])
  10. DataStreamScan(id=[2], fields=[count, word])
  11. == Physical Execution Plan ==
  12. Stage 1 : Data Source
  13. content : collect elements with CollectionInputFormat
  14. Stage 2 : Data Source
  15. content : collect elements with CollectionInputFormat
  16. Stage 3 : Operator
  17. content : from: (count, word)
  18. ship_strategy : REBALANCE
  19. Stage 4 : Operator
  20. content : where: (LIKE(word, _UTF-16LE'F%')), select: (count, word)
  21. ship_strategy : FORWARD
  22. Stage 5 : Operator
  23. content : from: (count, word)
  24. ship_strategy : REBALANCE
  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. == Optimized Logical Plan ==
  7. DataStreamUnion(all=[true], union all=[count, word])
  8. DataStreamCalc(select=[count, word], where=[LIKE(word, _UTF-16LE'F%')])
  9. DataStreamScan(id=[1], fields=[count, word])
  10. DataStreamScan(id=[2], fields=[count, word])
  11. == Physical Execution Plan ==
  12. Stage 1 : Data Source
  13. content : collect elements with CollectionInputFormat
  14. Stage 2 : Data Source
  15. content : collect elements with CollectionInputFormat
  16. Stage 3 : Operator
  17. content : from: (count, word)
  18. ship_strategy : REBALANCE
  19. Stage 4 : Operator
  20. content : where: (LIKE(word, _UTF-16LE'F%')), select: (count, word)
  21. ship_strategy : FORWARD
  22. Stage 5 : Operator
  23. content : from: (count, word)
  24. ship_strategy : REBALANCE
  1. == Abstract Syntax Tree ==
  2. LogicalUnion(all=[true])
  3. LogicalFilter(condition=[LIKE($1, _UTF-16LE'F%')])
  4. FlinkLogicalDataStreamScan(id=[3], fields=[count, word])
  5. FlinkLogicalDataStreamScan(id=[6], fields=[count, word])
  6. == Optimized Logical Plan ==
  7. DataStreamUnion(all=[true], union all=[count, word])
  8. DataStreamCalc(select=[count, word], where=[LIKE(word, _UTF-16LE'F%')])
  9. DataStreamScan(id=[3], fields=[count, word])
  10. DataStreamScan(id=[6], fields=[count, word])
  11. == Physical Execution Plan ==
  12. Stage 1 : Data Source
  13. content : collect elements with CollectionInputFormat
  14. Stage 2 : Operator
  15. content : Flat Map
  16. ship_strategy : FORWARD
  17. Stage 3 : Operator
  18. content : Map
  19. ship_strategy : FORWARD
  20. Stage 4 : Data Source
  21. content : collect elements with CollectionInputFormat
  22. Stage 5 : Operator
  23. content : Flat Map
  24. ship_strategy : FORWARD
  25. Stage 6 : Operator
  26. content : Map
  27. ship_strategy : FORWARD
  28. Stage 7 : Operator
  29. content : Map
  30. ship_strategy : FORWARD
  31. Stage 8 : Operator
  32. content : where: (LIKE(word, _UTF-16LE'F%')), select: (count, word)
  33. ship_strategy : FORWARD
  34. Stage 9 : Operator
  35. content : Map
  36. ship_strategy : FORWARD

The following code shows an example and the corresponding output for multiple-sinks plan using explain():

  1. EnvironmentSettings settings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build();
  2. TableEnvironment tEnv = TableEnvironment.create(settings);
  3. String[] fieldNames = { "count", "word" };
  4. TypeInformation[] fieldTypes = { Types.INT, Types.STRING };
  5. tEnv.registerTableSource("MySource1", new CsvTableSource("/source/path1", fieldNames, fieldTypes));
  6. tEnv.registerTableSource("MySource2", new CsvTableSource("/source/path2", fieldNames, fieldTypes));
  7. tEnv.registerTableSink("MySink1", new CsvTableSink("/sink/path1").configure(fieldNames, fieldTypes));
  8. tEnv.registerTableSink("MySink2", new CsvTableSink("/sink/path2").configure(fieldNames, fieldTypes));
  9. Table table1 = tEnv.scan("MySource1").where("LIKE(word, 'F%')");
  10. table1.insertInto("MySink1");
  11. Table table2 = table1.unionAll(tEnv.scan("MySource2"));
  12. table2.insertInto("MySink2");
  13. String explanation = tEnv.explain(false);
  14. System.out.println(explanation);
  1. val settings = EnvironmentSettings.newInstance.useBlinkPlanner.inStreamingMode.build
  2. val tEnv = TableEnvironment.create(settings)
  3. val fieldNames = Array("count", "word")
  4. val fieldTypes = Array[TypeInformation[_]](Types.INT, Types.STRING)
  5. tEnv.registerTableSource("MySource1", new CsvTableSource("/source/path1", fieldNames, fieldTypes))
  6. tEnv.registerTableSource("MySource2", new CsvTableSource("/source/path2",fieldNames, fieldTypes))
  7. tEnv.registerTableSink("MySink1", new CsvTableSink("/sink/path1").configure(fieldNames, fieldTypes))
  8. tEnv.registerTableSink("MySink2", new CsvTableSink("/sink/path2").configure(fieldNames, fieldTypes))
  9. val table1 = tEnv.scan("MySource1").where("LIKE(word, 'F%')")
  10. table1.insertInto("MySink1")
  11. val table2 = table1.unionAll(tEnv.scan("MySource2"))
  12. table2.insertInto("MySink2")
  13. val explanation = tEnv.explain(false)
  14. println(explanation)
  1. settings = EnvironmentSettings.new_instance().use_blink_planner().in_streaming_mode().build()
  2. t_env = TableEnvironment.create(environment_settings=settings)
  3. field_names = ["count", "word"]
  4. field_types = [DataTypes.INT(), DataTypes.STRING()]
  5. t_env.register_table_source("MySource1", CsvTableSource("/source/path1", field_names, field_types))
  6. t_env.register_table_source("MySource2", CsvTableSource("/source/path2", field_names, field_types))
  7. t_env.register_table_sink("MySink1", CsvTableSink("/sink/path1", field_names, field_types))
  8. t_env.register_table_sink("MySink2", CsvTableSink("/sink/path2", field_names, field_types))
  9. table1 = t_env.scan("MySource1").where("LIKE(word, 'F%')")
  10. table1.insert_into("MySink1")
  11. table2 = table1.union_all(t_env.scan("MySource2"))
  12. table2.insert_into("MySink2")
  13. explanation = t_env.explain()
  14. print(explanation)

the result of multiple-sinks plan is

  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. LogicalSink(name=[MySink2], fields=[count, word])
  6. +- LogicalUnion(all=[true])
  7. :- LogicalFilter(condition=[LIKE($1, _UTF-16LE'F%')])
  8. : +- LogicalTableScan(table=[[default_catalog, default_database, MySource1, source: [CsvTableSource(read fields: count, word)]]])
  9. +- LogicalTableScan(table=[[default_catalog, default_database, MySource2, source: [CsvTableSource(read fields: count, word)]]])
  10. == Optimized Logical Plan ==
  11. Calc(select=[count, word], where=[LIKE(word, _UTF-16LE'F%')], reuse_id=[1])
  12. +- TableSourceScan(table=[[default_catalog, default_database, MySource1, source: [CsvTableSource(read fields: count, word)]]], fields=[count, word])
  13. Sink(name=[MySink1], fields=[count, word])
  14. +- Reused(reference_id=[1])
  15. Sink(name=[MySink2], fields=[count, word])
  16. +- Union(all=[true], union=[count, word])
  17. :- Reused(reference_id=[1])
  18. +- TableSourceScan(table=[[default_catalog, default_database, MySource2, source: [CsvTableSource(read fields: count, word)]]], fields=[count, word])
  19. == Physical Execution Plan ==
  20. Stage 1 : Data Source
  21. content : collect elements with CollectionInputFormat
  22. Stage 2 : Operator
  23. content : CsvTableSource(read fields: count, word)
  24. ship_strategy : REBALANCE
  25. Stage 3 : Operator
  26. content : SourceConversion(table:Buffer(default_catalog, default_database, MySource1, source: [CsvTableSource(read fields: count, word)]), fields:(count, word))
  27. ship_strategy : FORWARD
  28. Stage 4 : Operator
  29. content : Calc(where: (word LIKE _UTF-16LE'F%'), select: (count, word))
  30. ship_strategy : FORWARD
  31. Stage 5 : Operator
  32. content : SinkConversionToRow
  33. ship_strategy : FORWARD
  34. Stage 6 : Operator
  35. content : Map
  36. ship_strategy : FORWARD
  37. Stage 8 : Data Source
  38. content : collect elements with CollectionInputFormat
  39. Stage 9 : Operator
  40. content : CsvTableSource(read fields: count, word)
  41. ship_strategy : REBALANCE
  42. Stage 10 : Operator
  43. content : SourceConversion(table:Buffer(default_catalog, default_database, MySource2, source: [CsvTableSource(read fields: count, word)]), fields:(count, word))
  44. ship_strategy : FORWARD
  45. Stage 12 : Operator
  46. content : SinkConversionToRow
  47. ship_strategy : FORWARD
  48. Stage 13 : Operator
  49. content : Map
  50. ship_strategy : FORWARD
  51. Stage 7 : Data Sink
  52. content : Sink: CsvTableSink(count, word)
  53. ship_strategy : FORWARD
  54. Stage 14 : Data Sink
  55. content : Sink: CsvTableSink(count, word)
  56. ship_strategy : FORWARD