Apache Flink · FLK-09
Streaming patterns — dedup, top-N, split/merge, MATCH_RECOGNIZE, OVER
- 5 h
- requires FLK-04
From your world. the recurring SQL shapes you already write (QUALIFY row_number dedup, window functions), now as streaming queries whose output is a changelog
Five streaming SQL patterns, written test-first
Implement five Flink SQL queries, one for each streaming pattern. Each query is a function in patterns.py
that returns a SQL string. The test runs your SQL on an embedded Flink TableEnvironment
(the flink_sql fixture) over a fixed source. It grades the changelog (the RowKinds that the query emits)
and the final materialized state against an independent DuckDB answer. It never grades the SQL text, so
any query that computes the right answer passes.
The fixed source
For dedup_keep_first, top_n and split_merge, the test creates this view before it runs your
query:
CREATE VIEW ord AS SELECT * FROM (VALUES
('A', 'o1', 10), ('A', 'o1', 10), ('A', 'o2', 50), ('A', 'o3', 30),
('B', 'o4', 20), ('B', 'o4', 20), ('B', 'o5', 90)
) AS t(company, order_id, amount);
Columns: company STRING, order_id STRING, amount INT. Note that o1 and o4 appear twice.
For match_recognize and over_running_sum, the test creates an event-time table ev
(backed by a CSV that the test writes). It has the columns g STRING, v INT, ts TIMESTAMP(3) and a
watermark on ts.
What to implement in patterns.py
-
dedup_keep_first() -> str— Fromord, keep exactly one row perorder_id: the first one that the query sees (ties broken byamountascending). Return the columnsorder_id, amount. (Append-only.) -
top_n(n: int) -> str— Fromord, return the topnorders byamountper company. Return the columnscompany, amount. (Updating.) -
split_merge() -> str— Classify each row ofordas'large'whenamount >= 50, else'small'. Write it as two filtered SELECTs that you merge together. Return the columnsorder_id, bucket, wherebucketis'large'/'small'. (Append-only. Keep duplicates.) -
match_recognize() -> str— Overev, find every pair of consecutive rows (perg, intsorder) where the value strictly increases. Return the columnsg, v1, v2. They are the earlier value and the later (larger) value. -
over_running_sum() -> str— Overev, emitg, v, run_sumfor every row.run_sumis the running SUM ofvperg, ordered byts(unbounded preceding to current row). (Append-only, one row out for each row in.)
Constraints
- Return the SQL as a string. Do not execute it yourself.
- Use the exact table/view and column names above.
- The test checks the changelog kinds (for example, dedup and OVER must be append-only, and Top-N must retract) and the final state against DuckDB. The column order does not matter, only the names that the task requests.
Starter code
"""Starter — fill in each pattern's SQL. These stubs are wrong on purpose so the
tests fail until you implement them (G1). See README for the source tables and
the required output columns.
"""
def dedup_keep_first() -> str:
# TODO: keep one row per order_id (the first), append-only.
return "SELECT order_id, amount FROM ord"
def top_n(n: int) -> str:
# TODO: top-n by amount per company (an updating query).
return "SELECT company, amount FROM ord"
def split_merge() -> str:
# TODO: label each row large/small and UNION ALL the two halves.
return "SELECT order_id, 'small' AS bucket FROM ord"
def match_recognize() -> str:
# TODO: consecutive increasing pairs per g via MATCH_RECOGNIZE.
return "SELECT g, v AS v1, v AS v2 FROM ev"
def over_running_sum() -> str:
# TODO: running SUM per g ordered by ts.
return "SELECT g, v, v AS run_sum FROM ev"
Hints
Hint 1
Dedup keep-first: ROW_NUMBER() OVER (PARTITION BY key ORDER BY <time> ASC) and keep rn = 1. For Top-N use ORDER BY <value> DESC and keep rn <= n.
Hint 2
Split = two SELECTs with different WHEREs; merge = UNION ALL. MATCH_RECOGNIZE needs PARTITION BY, ORDER BY a time attribute, PATTERN (A B), DEFINE B AS B.v > A.v, MEASURES ... ONE ROW PER MATCH.
Hint 3
OVER running sum: SUM(v) OVER (PARTITION BY g ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW). Return the SQL as a string; the source table/view names are fixed by the test (see README).
uv run de start FLK-09/01-five-patterns
# edit workspace/FLK-09/01-five-patterns/
uv run de test