Back to Apache Flink
Q12Question 12 of 33

What is MATCH_RECOGNIZE in Flink SQL

Key Concepts
  • MATCH_RECOGNIZE detects sequence patterns in an ordered stream using regex-like PATTERN and DEFINE clauses.
  • PARTITION BY splits the stream into independent sequences, and ORDER BY orders by a time attribute.
  • Quantifiers like + (one or more), * (zero or more), and ? (optional) control how many rows each variable matches.
  • DEFINE gives each variable a boolean condition, and MEASURES computes output columns from matched rows.
  • AFTER MATCH SKIP controls where matching resumes and affects overlapping matches and state growth.
  • The within constraint bounds the pattern to a time window, keeping partial-match state finite.
  • MATCH_RECOGNIZE is the declarative SQL alternative to Flink's lower-level CEP library.

Definition

MATCH_RECOGNIZE detects sequence patterns in an ordered partitioned stream using regex-like PATTERN and DEFINE clauses, turning complex event processing into declarative SQL.
  • MATCH_RECOGNIZE – The ANSI SQL construct for complex event processing, detecting patterns across ordered rows.
  • PATTERN – A regex-like expression over named row variables with quantifiers like A B+ C.
  • DEFINE – Gives each variable a boolean condition that a row must satisfy to be bound to that variable.
  • MEASURES – Computes output columns from the matched rows using navigation functions like FIRST and LAST.
  • AFTER MATCH SKIP – Controls where matching resumes after a match, affecting overlapping detection and state growth.

The National Bank of India's trading desk wants to spot a shape in a stock's price: the price drops, stays low for a while, then recovers. MATCH_RECOGNIZE is like giving a watchful analyst a pattern written in shorthand: "first a down tick, then one or more low ticks, then an up tick." The analyst scans the ordered stream and raises a flag every time that exact shape appears.

Understanding the Concepts

What is MATCH_RECOGNIZE?

MATCH_RECOGNIZE is the ANSI SQL construct for complex event processing in Flink SQL. It sits in the FROM clause over a table and detects patterns across a sequence of rows.

SQL
-- Detect a drop-low-recovery pattern in stock prices
SELECT *
FROM stock_prices
MATCH_RECOGNIZE (
    PARTITION BY symbol
    ORDER BY event_time
    MEASURES
        A.price AS start_price,
        LAST(B.price) AS lowest_price,
        C.price AS recovery_price,
        A.event_time AS pattern_start,
        C.event_time AS pattern_end
    ONE ROW PER MATCH
    AFTER MATCH SKIP PAST LAST ROW
    PATTERN (A B+ C)
    DEFINE
        A AS A.price > 100,
        B AS B.price < A.price,
        C AS C.price >= A.price
);

What are the clause components?

Each clause in MATCH_RECOGNIZE has a specific role.

SQL
-- Breaking down each clause:
SELECT * FROM loan_defaults
MATCH_RECOGNIZE (
    -- Split into independent sequences per loan
    PARTITION BY loan_id

    -- Order by time (required for pattern detection)
    ORDER BY payment_date

    -- Output columns from matched rows
    MEASURES
        A.payment_date AS first_missed,
        LAST(B.payment_date) AS last_missed,
        C.payment_date AS partial_payment_date,
        COUNT(B.payment_date) AS consecutive_misses

    -- One summary row per match
    ONE ROW PER MATCH

    -- Resume matching after the last matched row
    AFTER MATCH SKIP PAST LAST ROW

    -- Pattern: one missed payment, then more missed, then a partial payment
    PATTERN (A B+ C)

    -- Conditions for each variable
    DEFINE
        A AS A.amount_paid = 0,
        B AS B.amount_paid = 0,
        C AS C.amount_paid > 0 AND C.amount_paid < C.emi_due
);

How do quantifiers work?

Quantifiers control how many rows each variable can match.

SQL
-- Quantifier examples:
-- A       exactly one row
-- B+      one or more rows (greedy)
-- B*      zero or more rows (greedy)
-- B?      zero or one row
-- B{3}    exactly 3 rows
-- B{2,5}  between 2 and 5 rows

-- Detect 3 or more consecutive failed payments on a loan
SELECT * FROM payment_events
MATCH_RECOGNIZE (
    PARTITION BY loan_id
    ORDER BY payment_date
    MEASURES
        FIRST(A.payment_date) AS first_failure,
        LAST(A.payment_date) AS last_failure,
        COUNT(A.payment_date) AS failure_count
    ONE ROW PER MATCH
    AFTER MATCH SKIP PAST LAST ROW
    PATTERN (A{3,})
    DEFINE
        A AS A.status = 'FAILED'
);

How does DEFINE reference earlier matched rows?

Conditions in DEFINE can reference the current row and previously matched rows, enabling relative logic.

JAVA
package com.bank.flink;

import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;

public class MatchRecognizeDemo {
    public static void main(String[] args) {
        TableEnvironment tEnv = TableEnvironment.create(
            EnvironmentSettings.inStreamingMode());

        // Register stock_prices table with event-time watermark
        // ...

        // Detect drop-then-recovery pattern
        tEnv.executeSql(
            "SELECT * FROM stock_prices " +
            "MATCH_RECOGNIZE (" +
            "  PARTITION BY symbol " +
            "  ORDER BY event_time " +
            "  MEASURES " +
            "    A.price AS start_price, " +
            "    LAST(B.price) AS low_price, " +
            "    C.price AS recovery_price " +
            "  ONE ROW PER MATCH " +
            "  AFTER MATCH SKIP PAST LAST ROW " +
            "  PATTERN (A B+ C) " +
            "  DEFINE " +
            "    B AS B.price < A.price, " +   // B references A's price
            "    C AS C.price >= A.price" +     // C references A's price
            ")"
        );
    }
}

What does AFTER MATCH SKIP do?

AFTER MATCH SKIP controls where matching resumes after a match completes. It affects overlapping matches and state growth.

SQL
-- AFTER MATCH SKIP PAST LAST ROW:
--   Resume matching after the last row of the current match.
--   No overlapping matches. Lower state cost.

-- AFTER MATCH SKIP TO NEXT ROW:
--   Resume from the row after the first row of the match.
--   Allows overlapping matches. Higher state cost.

-- AFTER MATCH SKIP TO LAST B:
--   Resume from the last row bound to variable B.
--   Useful for chained patterns.

What is the banking use case for loan default detection?

At the National Bank of India, MATCH_RECOGNIZE detects a sequence of missed payments followed by a partial payment, signaling a loan at risk of default.

SQL
-- Loan default early warning pattern
CREATE TABLE emi_payments (
    loan_id      STRING,
    emi_amount   DECIMAL(15, 2),
    paid_amount  DECIMAL(15, 2),
    due_date     TIMESTAMP(3),
    WATERMARK FOR due_date AS due_date - INTERVAL '1' DAY
) WITH ('connector' = 'kafka', 'topic' = 'nbi-emi-payments',
        'format' = 'json',
        'properties.bootstrap.servers' = 'kafka.bank.internal:9092');

SELECT * FROM emi_payments
MATCH_RECOGNIZE (
    PARTITION BY loan_id
    ORDER BY due_date
    MEASURES
        FIRST(A.due_date) AS first_missed_date,
        LAST(A.due_date) AS last_missed_date,
        COUNT(A.due_date) AS missed_count,
        B.paid_amount AS partial_amount
    ONE ROW PER MATCH
    AFTER MATCH SKIP PAST LAST ROW
    PATTERN (A{2,} B)
    DEFINE
        A AS A.paid_amount = 0,
        B AS B.paid_amount > 0 AND B.paid_amount < B.emi_amount
);
DIAGRAM
Example match:
  Loan L-001:
    Jan: paid 0      → A (missed)
    Feb: paid 0      → A (missed)
    Mar: paid 0      → A (missed)
    Apr: paid 5000   → B (partial, less than EMI of 15000)
  Match: first_missed=Jan, last_missed=Mar, missed_count=3, partial=5000

What about state management?

Partial-match state can grow if patterns are open-ended. Bounding the pattern and using AFTER MATCH SKIP PAST LAST ROW keeps state finite.

SQL
-- WRONG: unbounded pattern with no skip control
-- PATTERN (A B*) with SKIP TO NEXT ROW
-- → keeps many partial matches alive → state explosion

-- CORRECT: bounded pattern with skip past last row
-- PATTERN (A B{1,10} C) with SKIP PAST LAST ROW
-- → at most 10 rows per partial match → bounded state

What should a senior engineer know?

  • MATCH_RECOGNIZE runs on event time with watermarks, so the ORDER BY column must have a watermark.
  • Greedy quantifiers (default) match as many rows as possible. Reluctant quantifiers match as few.
  • MATCH_RECOGNIZE is the declarative alternative to Flink's CEP library and hand-written ProcessFunctions.
  • Very dynamic patterns that change at runtime are a limitation: the pattern is compiled statically.
  • ONE ROW PER MATCH produces a summary; ALL ROWS PER MATCH produces one row per matched input.
JAVA
// Senior tip: EXPLAIN the MATCH_RECOGNIZE query to see the physical plan
tEnv.executeSql("EXPLAIN SELECT * FROM stock_prices MATCH_RECOGNIZE (...)");
// Shows the NFA operator and state requirements

Visual Overview

DIAGRAM
  MATCH_RECOGNIZE Clause Structure

  SELECT * FROM table_name
  MATCH_RECOGNIZE (
      ┌─────────────────────────────────────┐
      │  PARTITION BY symbol                 │  ← split per key
      │  ORDER BY event_time                 │  ← time ordering
      │                                      │
      │  MEASURES                            │  ← output columns
      │    A.price AS start_price             │
      │    LAST(B.price) AS low_price         │
      │    C.price AS recovery_price          │
      │                                      │
      │  ONE ROW PER MATCH                   │  ← output shape
      │  AFTER MATCH SKIP PAST LAST ROW      │  ← resume point
      │                                      │
      │  PATTERN (A B+ C)                    │  ← regex pattern
      │                                      │
      │  DEFINE                              │  ← conditions
      │    B AS B.price < A.price             │
      │    C AS C.price >= A.price            │
      └─────────────────────────────────────┘
  )
DIAGRAM
  Pattern Matching Timeline (Drop-Low-Recovery)

  Price ▲
  150   │  ●(A)
  140   │       ●
  120   │            ●(B)
  110   │                 ●(B)
  100   │                      ●(B)
  130   │                           ●
  155   │                                ●(C)
        └──────────────────────────────────▶ Time
             A    B    B    B         C
         start  ───sustained low───  recovery

Key Takeaway

MATCH_RECOGNIZE is the ANSI SQL construct for complex event processing in Flink SQL. It detects patterns across ordered rows using a regex-like PATTERN clause with named variables and quantifiers, a DEFINE clause giving each variable a boolean condition, and a MEASURES clause computing output columns from matched rows. It runs on event time with watermarks and tracks partial matches per partition in keyed state.

The canonical use case is detecting sequences like a price drop followed by a sustained low followed by a recovery. You write PATTERN (A B+ C), define B as a price below A's starting price, and define C as a recovery back above A. AFTER MATCH SKIP PAST LAST ROW prevents overlapping re-detection and keeps partial-match state bounded.

At the National Bank of India, we used MATCH_RECOGNIZE for loan default early warning: detecting two or more consecutive missed EMI payments followed by a partial payment. The pattern fired the instant the shape completed, and because it ran on event time with watermarks, matches were deterministic and auditable. Bounding the pattern with a maximum count kept state manageable.

Remember one sentence: MATCH_RECOGNIZE turns complex event processing into declarative SQL by detecting regex-like sequence patterns across an ordered, partitioned stream with PATTERN, DEFINE, and MEASURES clauses.

---

Share
Use ← → arrow keys to navigate