Definition
- Flink SQL – The declarative ANSI-SQL layer of Apache Flink for both streaming and batch computations.
- Dynamic table – A table that changes continuously as new data arrives, backed by connectors like Kafka, Hive, or JDBC.
- Streaming features – Windowed aggregations (TUMBLE, HOP, CUMULATE), temporal joins (FOR SYSTEM_TIME AS OF), MATCH_RECOGNIZE, and CDC.
- Changelog output – Query results are append-only, retract, or upsert streams depending on semantics.
- True continuous processing – Each event is processed incrementally, unlike Spark Structured Streaming's micro-batch default.
The National Bank of India has analysts who know SQL but not low-level stream programming. Flink SQL lets them write a familiar query such as "sum transactions per branch per hour" and have it run forever against the live feed, with the answer updating automatically as new transactions flow in. They do not code operators or timers; they describe the result in SQL and Flink maintains it continuously.
Understanding the Concepts
What is Flink SQL?
Flink SQL is the declarative SQL layer of Apache Flink. It lets you write standard ANSI SQL that runs continuously over streams or once over bounded data.
-- Continuous query: sum deposits per branch per hour
SELECT
branch_id,
TUMBLE_START(event_time, INTERVAL '1' HOUR) AS window_start,
SUM(amount) AS hourly_deposits
FROM transactions
WHERE txn_type = 'DEPOSIT'
GROUP BY branch_id, TUMBLE(event_time, INTERVAL '1' HOUR);
-- Runs forever, emitting results as each hour's window closes
What are dynamic tables?
The central concept is the dynamic table, a table that changes as events arrive. A query over it produces a continuously updating result through the stream-table duality.
-- Dynamic table backed by Kafka
CREATE TABLE transactions (
txn_id STRING,
account_id STRING,
branch_id STRING,
amount DECIMAL(15, 2),
txn_type STRING,
event_time TIMESTAMP(3),
WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND
) WITH (
'connector' = 'kafka',
'topic' = 'nbi-transactions',
'format' = 'json',
'properties.bootstrap.servers' = 'kafka.bank.internal:9092'
);
package com.bank.flink;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;
public class FlinkSqlDynamicTableDemo {
public static void main(String[] args) {
TableEnvironment tEnv = TableEnvironment.create(
EnvironmentSettings.inStreamingMode());
// Register dynamic table backed by Kafka
tEnv.executeSql("CREATE TABLE transactions (...) WITH ('connector'='kafka', ...)");
// Continuous query over dynamic table
tEnv.executeSql(
"SELECT branch_id, SUM(amount) AS total " +
"FROM transactions GROUP BY branch_id"
);
}
}
What streaming features does Flink SQL support?
Flink SQL supports windowed aggregations, temporal joins, MATCH_RECOGNIZE pattern recognition, and CDC connectors.
-- Windowed aggregation with TUMBLE
SELECT branch_id, TUMBLE_START(event_time, INTERVAL '1' HOUR), SUM(amount)
FROM transactions GROUP BY branch_id, TUMBLE(event_time, INTERVAL '1' HOUR);
-- Temporal join for point-in-time exchange rate lookup
SELECT t.txn_id, t.amount * r.rate_to_inr AS amount_inr
FROM forex_transactions t
JOIN exchange_rates FOR SYSTEM_TIME AS OF t.event_time AS r
ON t.currency = r.currency;
-- MATCH_RECOGNIZE for pattern detection
SELECT * FROM stock_prices
MATCH_RECOGNIZE (
PARTITION BY symbol ORDER BY event_time
PATTERN (A B+ C)
DEFINE B AS B.price < A.price, C AS C.price >= A.price
);
-- CDC source for real-time database sync
CREATE TABLE accounts_cdc (
account_id STRING, balance DECIMAL(15,2),
PRIMARY KEY (account_id) NOT ENFORCED
) WITH ('connector' = 'mysql-cdc', 'hostname' = 'mysql.bank.internal',
'database-name' = 'nbi', 'table-name' = 'accounts');
What are the three result stream kinds?
A continuous query produces one of three changelog kinds depending on its semantics.
Append-only: filters, projections, window aggregations (final result per window) → +I (ACCT-100, 50000) only inserts Retract: non-windowed aggregations without a keyed sink → +I (ACCT-100, 50000) insert → -U (ACCT-100, 50000) retract old → +U (ACCT-100, 80000) insert new Upsert: keyed aggregations or CDC feeds → +U (ACCT-100, 50000) upsert (single message) → +U (ACCT-100, 80000) upsert (replaces)
When do you use Flink SQL vs the DataStream API?
Use Flink SQL when the team is SQL-fluent and the computation fits relational operations. Drop to the DataStream API only when you need custom timers, arbitrary state logic, or fine-grained control.
| Feature | Flink SQL | DataStream API |
|---|---|---|
| Language | SQL | Java / Scala / Python |
| Best for | Filters, joins, aggregations, patterns | Custom state, timers, side outputs |
| Learning curve | SQL knowledge | Flink programming model |
| Optimization | Calcite cost-based optimizer | Manual tuning |
| Extensibility | UDFs, MATCH_RECOGNIZE | Full control |
How does Flink SQL differ from Spark SQL for streaming?
Flink SQL runs a long-lived continuous query processing each event incrementally. Spark Structured Streaming models the stream as an unbounded table but executes as micro-batches by default.
Flink SQL (true streaming): Event arrives → processed immediately → result updated Latency: milliseconds to low seconds Changelog: native retract/upsert semantics CDC: first-class temporal joins and changelog support Spark Structured Streaming (micro-batch default): Events accumulate → processed as a batch on trigger interval Latency: seconds to minutes (batch interval) Changelog: less native; no first-class retract semantics CDC: supported but less integrated
// Flink: continuous query, event-at-a-time processing
tEnv.executeSql("SELECT account_id, SUM(amount) FROM transactions GROUP BY account_id");
// Each transaction updates the aggregate immediately
// Spark: micro-batch, processes accumulated events per trigger
// spark.readStream.format("kafka").load()
// .groupBy("account_id").agg(sum("amount"))
// .writeStream.trigger(processingTime="10 seconds").start()
// Updates happen every 10 seconds
What about state management in Flink SQL?
Stateful operators accumulate state. For unbounded key spaces, set idle-state retention to prevent unbounded growth.
-- Set state TTL to 24 hours
SET 'table.exec.state.ttl' = '86400000';
SELECT account_id, SUM(amount)
FROM transactions
GROUP BY account_id;
-- Accounts not seen in 24h have their state cleaned up
What should a senior engineer know?
- Know which result stream kind your query produces: append-only queries go to any sink, updating queries need upsert sinks.
- Bound state with idle-state retention for unbounded joins and aggregations.
- Watermarks are as important in SQL as in the DataStream API; windows and temporal joins depend on them.
- Choose Flink SQL for true low-latency streaming with native changelog needs.
- Choose Spark SQL when a unified Spark ecosystem and second-level latency suffice.
// Senior tip: use EXPLAIN to understand the physical plan
tEnv.executeSql("EXPLAIN SELECT branch_id, SUM(amount) FROM transactions GROUP BY branch_id");
// Shows: changelog mode, state operators, optimization choices
Visual Overview
Flink SQL vs Spark SQL – Execution Model
Flink SQL (continuous): Spark SQL (micro-batch):
┌──────────────────┐ ┌──────────────────┐
│ Event arrives │ │ Events accumulate │
│ → process NOW │ │ for trigger │
│ → update result │ │ interval │
└──────┬───────────┘ └──────┬───────────┘
│ │
▼ ▼
Latency: ms Latency: seconds-minutes
True streaming Micro-batch
Native changelog Limited changelog
Flink SQL Feature Set ┌──────────────────────────────────────────┐ │ Flink SQL │ │ │ │ ┌──────────┐ ┌──────────┐ ┌─────────┐│ │ │ Windowed │ │ Temporal │ │ MATCH_ ││ │ │ Agg │ │ Join │ │ RECOGNIZE││ │ │ TUMBLE │ │ FOR │ │ PATTERN ││ │ │ HOP │ │ SYSTEM │ │ DEFINE ││ │ │ CUMULATE │ │ TIME │ │ MEASURES││ │ └──────────┘ └──────────┘ └─────────┘│ │ │ │ ┌──────────┐ ┌──────────┐ ┌─────────┐│ │ │ CDC │ │ Catalogs │ │ Hints ││ │ │ mysql-cdc │ │ Hive │ │ OPTIONS ││ │ │ debezium │ │ JDBC │ │ LOOKUP ││ │ └──────────┘ └──────────┘ └─────────┘│ └──────────────────────────────────────────┘
Key Takeaway
Flink SQL is the declarative ANSI-SQL layer of Apache Flink that lets you express both streaming and batch computations as SQL over dynamic tables. The central idea is the stream-table duality: a stream is a changelog of a table, and a query over a dynamic table produces a continuously updating result as append-only, retract, or upsert changelog streams. Flink SQL supports windowed aggregations, temporal joins for point-in-time enrichment, MATCH_RECOGNIZE for pattern recognition, and native CDC connectors.
You reach for Flink SQL when the team is SQL-fluent and the computation fits relational operations. You drop to the DataStream API only when you need custom timers or arbitrary state logic. The difference from Spark SQL matters most for streaming: Flink SQL processes each event incrementally with true continuous processing, while Spark executes as micro-batches by default with higher minimum latency.
At the National Bank of India our analytics team wrote a straightforward GROUP BY branch SUM query and Flink maintained it forever against the live Kafka feed, emitting upsert updates so each new transaction updated the affected branch's total in place. When the team needed point-in-time currency conversion they used a temporal join, and when they needed pattern detection they used MATCH_RECOGNIZE, all without leaving SQL.
Remember one sentence: Flink SQL is continuous incremental SQL processing over dynamic tables with native changelog semantics, temporal joins, and pattern recognition, and you choose it over the DataStream API when the computation fits relational operations.
---