【Oracle】MATCH_RECOGNIZE完全ガイド|行パターンマッチングで時系列データのイベントを SQL で検出する

Oracle 12c で導入された MATCH_RECOGNIZE は、正規表現のような構文を使って行の並びに対してパターンマッチングを行う SQL 機能です。標準 SQL:2016 にも採用されており、Oracle が最も早く実装した高度な分析機能のひとつです。

通常のウィンドウ関数(ROW_NUMBER / LAG / LEAD)では複雑になる「連続した行のパターン検出」を、MATCH_RECOGNIZE を使うと直感的に記述できます。株価の上昇→頂点→下降パターン検出・ユーザーのセッション時間集計・センサーデータの異常値パターン検出などに活用できます。

この記事でわかること

  • MATCH_RECOGNIZE の基本構文(PARTITION BY / ORDER BY / MEASURES / PATTERN / DEFINE)
  • PATTERN 句で行のパターンを正規表現ライクに記述する方法
  • DEFINE 句でパターン変数の条件を定義する方法
  • MEASURES 句でマッチした行から集計値を取り出す方法
  • ONE ROW PER MATCH と ALL ROWS PER MATCH の違い
  • AFTER MATCH SKIP でマッチ後の再開位置を制御する方法
  • 実用例: 株価パターン検出・セッション集計
スポンサーリンク

MATCH_RECOGNIZE の基本構文

MATCH_RECOGNIZE の基本構文と各句の役割
SELECT *
FROM テーブル名
MATCH_RECOGNIZE (
    PARTITION BY 列名           -- グループ分けの基準(省略可)
    ORDER BY 列名               -- 行の順序を定義する(必須)

    MEASURES                    -- マッチ結果から取り出す値を定義する
        A.列名 AS 別名,
        LAST(B.列名) AS 別名

    ONE ROW PER MATCH           -- マッチごとに1行を返す(デフォルト)
    -- ALL ROWS PER MATCH       -- マッチした全行を返す

    AFTER MATCH SKIP PAST LAST ROW  -- マッチ後の検索再開位置

    PATTERN ( A+ B+ )           -- 行パターンを正規表現で定義する

    DEFINE                      -- パターン変数の条件を定義する
        A AS A.price <= PREV(A.price),  -- A: 前の行以下
        B AS B.price > PREV(B.price)    -- B: 前の行より大きい
);

-- PATTERN で使える正規表現演算子
-- A       : パターン変数 A が1回一致
-- A+      : A が1回以上一致
-- A*      : A が0回以上一致
-- A?      : A が0回または1回一致
-- A | B   : A または B が一致
-- (A B)   : A の後に B
-- A{n}    : A が n 回一致
-- A{m,n}  : A が m 回以上 n 回以下

実用例:株価の上昇→頂点→下降パターンを検出する

株価データで「価格が上昇し続けた後、頂点を迎えて下降に転じる」パターンを検出します。これは LAG 関数の組み合わせでも実装できますが、MATCH_RECOGNIZE を使うと直感的に書けます。

株価の上昇→頂点→下降パターンを検出する
-- サンプルデータの想定:
-- stock_prices (ticker, trade_date, close_price)

SELECT
    ticker,
    pattern_start,
    pattern_end,
    peak_date,
    peak_price,
    start_price,
    end_price
FROM stock_prices
MATCH_RECOGNIZE (
    PARTITION BY ticker           -- 銘柄ごとにパターンを探す
    ORDER BY trade_date

    MEASURES
        FIRST(UP.trade_date)  AS pattern_start,  -- 上昇開始日
        LAST(DOWN.trade_date) AS pattern_end,     -- 下降終了日
        FIRST(DOWN.trade_date) AS peak_date,      -- 最初の下降日(頂点の翌日)
        LAST(UP.close_price)  AS peak_price,      -- 上昇最終値(頂点価格)
        FIRST(UP.close_price) AS start_price,     -- パターン開始価格
        LAST(DOWN.close_price) AS end_price       -- パターン終了価格

    ONE ROW PER MATCH
    AFTER MATCH SKIP TO LAST DOWN  -- 最後の DOWN 行から再開する

    PATTERN ( UP+ DOWN+ )          -- 1回以上の上昇後に1回以上の下降

    DEFINE
        UP   AS UP.close_price   > PREV(UP.close_price),    -- 前日より高い
        DOWN AS DOWN.close_price < PREV(DOWN.close_price)   -- 前日より低い
)
ORDER BY ticker, pattern_start;

-- 出力例:
-- TICKER  PATTERN_START  PATTERN_END  PEAK_DATE   PEAK_PRICE  START_PRICE  END_PRICE
-- AAPL    2024-01-05     2024-01-15   2024-01-10   195.0       185.0        175.0

実用例:ユーザーのセッション時間を集計する

ログデータからユーザーの「ログイン→操作→ログアウト」という一連のセッションを特定して、セッション時間を集計する例です。

ユーザーセッションの開始・終了・所要時間を集計する
-- サンプルデータの想定:
-- user_events (user_id, event_time, event_type)
-- event_type: LOGIN / ACTION / LOGOUT

SELECT
    user_id,
    session_start,
    session_end,
    ROUND((session_end - session_start) * 24 * 60, 1) AS duration_minutes,
    action_count
FROM user_events
MATCH_RECOGNIZE (
    PARTITION BY user_id
    ORDER BY event_time

    MEASURES
        LOGIN.event_time         AS session_start,    -- ログイン時刻
        LAST(LOGOUT.event_time)  AS session_end,      -- ログアウト時刻
        COUNT(ACTION.*)          AS action_count       -- セッション内の操作回数

    ONE ROW PER MATCH
    AFTER MATCH SKIP PAST LAST ROW

    PATTERN ( LOGIN ACTION* LOGOUT )  -- ログイン→0回以上の操作→ログアウト

    DEFINE
        LOGIN  AS LOGIN.event_type  = 'LOGIN',
        ACTION AS ACTION.event_type = 'ACTION',
        LOGOUT AS LOGOUT.event_type = 'LOGOUT'
)
ORDER BY user_id, session_start;

ONE ROW PER MATCH と ALL ROWS PER MATCH の違い

ALL ROWS PER MATCH でマッチした全行を取得する
-- ONE ROW PER MATCH: マッチごとに集計値を1行で返す(デフォルト)
-- ALL ROWS PER MATCH: マッチした各行に対して結果を返す(デバッグ・詳細分析に便利)

SELECT *
FROM stock_prices
MATCH_RECOGNIZE (
    PARTITION BY ticker
    ORDER BY trade_date

    MEASURES
        MATCH_NUMBER()           AS match_num,    -- 何番目のマッチか
        CLASSIFIER()             AS var_name,     -- この行がどのパターン変数か (UP/DOWN)
        close_price              AS price,
        FIRST(UP.trade_date)     AS match_start

    ALL ROWS PER MATCH            -- マッチした全行を個別に返す
    AFTER MATCH SKIP PAST LAST ROW

    PATTERN ( UP+ DOWN+ )

    DEFINE
        UP   AS UP.close_price   > PREV(UP.close_price),
        DOWN AS DOWN.close_price < PREV(DOWN.close_price)
)
WHERE ticker = 'AAPL'
ORDER BY trade_date;

-- MATCH_NUMBER(): このマッチが全体の何番目のマッチかを返す
-- CLASSIFIER(): 各行がどのパターン変数(UP/DOWN)に分類されているかを返す
-- これらは MEASURES 句または ALL ROWS PER MATCH 時に使える

AFTER MATCH SKIP でマッチ後の再開位置を制御する

オプション 動作 使用場面
SKIP PAST LAST ROW マッチした最後の行の次から再開する(重複なし) 重複しないマッチが必要なとき(デフォルト)
SKIP TO NEXT ROW マッチ開始行の次から再開する(重複マッチ可) 重複するマッチも検出したいとき
SKIP TO FIRST 変数名 マッチ内の指定変数の最初の行から再開する 特定の変数位置から再開したいとき
SKIP TO LAST 変数名 マッチ内の指定変数の最後の行から再開する 変数の末尾から次のマッチを探したいとき

まとめ

  • MATCH_RECOGNIZE:Oracle 12c 以降で使える行パターンマッチング。正規表現ライクな PATTERN 句で連続する行のパターンを検出できる
  • PARTITION BY / ORDER BY:グループ分けと行の順序を定義する。ORDER BY は必須
  • PATTERN 句A+(1回以上)/ A*(0回以上)/ A?(0または1回)/ |(OR)を使って行のパターンを定義する
  • DEFINE 句:各パターン変数が満たすべき条件を SQL で記述する。PREV() で1つ前の行を参照できる
  • MEASURES 句:マッチした行から集計値を取り出す。FIRST() / LAST() でマッチ内の先頭・末尾を取得できる
  • ONE ROW PER MATCH:マッチごとに1行を返す。ALL ROWS PER MATCH でマッチした全行を個別に取得できる
  • CLASSIFIER() / MATCH_NUMBER():ALL ROWS PER MATCH 時に各行の分類とマッチ番号を取得できる

時系列データの分析には分析関数(ウィンドウ関数)との組み合わせも有効です。Oracle 分析関数完全ガイドOracle 階層問合せ完全ガイドも参照してください。