SQL 異常検知 — ローリングz-score・複合シグナルの応用

応用異常検知ローリングz-scoreGaps and Islands季節性 / LAGROLLUP / FILTERPostgreSQL対応全5問
1 / 5 · 学習中 0 / 5 問完了
QUESTION 6

ローリングz-score — 直近N日の動的基準で異常を測り、当日除外と最小期間で誤検知を防ぐ

ROWS BETWEEN n PRECEDING当日除外 / leakageローリングz-score動的基準の異常検知
前提知識

z-score は全期間の固定平均・SDを基準にしました。しかし系列にトレンドや水準変化があると固定基準はすぐ陳腐化します。実務では「直近N日だけ」を基準にする移動z-score(rolling z)が定番です。鍵は2つ——当日を基準から除外すること(自分自身で自分を評価しない=leakage回避)と、基準が十分に貯まるまで判定を保留すること(min_periods)です。

AVG(amount)    OVER (ORDER BY dt ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING)
-- 「3 PRECEDING AND 1 PRECEDING」= 当日を含めず、直前3日だけを基準にする
-- これが「... AND CURRENT ROW」だと当日(=評価対象)が基準に混入してしまう
当日除外(leakage回避)が肝:フレーム終端を CURRENT ROW ではなく 1 PRECEDING にすると、当日の値は基準計算から外れます。異常な当日値が自分の基準平均・SDを押し上げて自分を正常に見せてしまう事故を防げます(フレーム指定の応用)。
問題

sensor_readings から、直前3日(当日除外)の移動平均 roll_avg・移動標準偏差 roll_std を求め、z =(amount − roll_avg)/roll_std を計算してください。ただし基準に使えた過去日数 n_base が3未満の行は判定保留('warmup')とし、それ以外は |z|≥2 で 'anomaly'、それ未満を 'normal' とします。出力列は dt, amount, roll_avg, roll_std, z_score, status、dt 昇順。平均・SD・z は小数第2位まで。

使用テーブル
► sensor_readings(9行)
dtamount
2024-01-01100
2024-01-0296
2024-01-03104
2024-01-04100
2024-01-0596
2024-01-06200
2024-01-07100
2024-01-0896
2024-01-09104
期待出力
dtamountroll_avgroll_stdz_scorestatus
2024-01-01100NULLNULLNULLwarmup
2024-01-0296100.00NULLNULLwarmup
2024-01-0310498.002.832.12warmup
2024-01-04100100.004.000.00normal
2024-01-0596100.004.00-1.00normal
2024-01-06200100.004.0025.00anomaly
2024-01-07100132.0058.92-0.54normal
2024-01-0896132.0058.92-0.61normal
2024-01-09104132.0058.92-0.48normal
QUESTION 7

連続異常の検知 — Gaps & Islands で『N日連続の異常』だけを束ねて拾う

ROW_NUMBER 差分トリック日付 − 連番Gaps & Islands持続的障害の検知
前提知識

単発の異常(点異常)と、「異常が何日も連続した=持続的な障害」は意味が違います。後者こそ本当に対応すべきインシデントです。連続する行を1つの塊(島=island)として束ねる定番技法が Gaps & Islands。核心は「連番との差分は、連続している間だけ一定になる」という性質です。

-- 異常日だけを日付順に並べ、連番(rn)を振ると…
dt:   01-05  01-06  01-07     01-10  01-11
rn:     1      2      3         4      5
dt-rn: 01-04  01-04  01-04     01-06  01-06   ← 連続する塊ごとに同じ値!
「日付 − 連番」が島キーになる仕組み:連続した日付は1日ずつ増え、連番も1ずつ増えるので、その差は一定になります。間が空く(gap)と差がズレて新しい島が始まります。この差分値で GROUP BY すれば、連続ランごとに集約できます(ROW_NUMBER と GROUP BY/HAVING の合わせ技)。
問題

service_health(日次のエラー率)から、error_rate > 5 を異常日と定義し、異常日が3日以上連続したラン(持続的障害)だけを抽出してください。各ランについて 開始日 start_dt・終了日 end_dt・連続日数 run_days・期間中の最大エラー率 peak_error を求め、start_dt 昇順で返してください。

使用テーブル
► service_health(12行)
dterror_rate
2024-01-012
2024-01-028
2024-01-033
2024-01-042
2024-01-059
2024-01-067
2024-01-076
2024-01-081
2024-01-092
2024-01-1010
2024-01-1112
2024-01-123
期待出力
start_dtend_dtrun_dayspeak_error
2024-01-052024-01-0739
QUESTION 8

季節性ベースライン — LAG(amount, 7) で『前週の同じ曜日』と比べ周期の罠を回避する

LAG(col, n)オフセット参照週次季節性周期を考慮した検知
前提知識

多くの指標は曜日の周期(週次季節性)を持ちます。平日は低く週末は高い、のような。これを「前日比」で監視すると、毎週末や毎週月曜に正常な周期変動が異常として誤報されます。対策は単純で、比較相手を「昨日」ではなく「1週間前の同じ曜日」にすること。LAG(amount, 7) で7行前を引いてくるだけです。

LAG(amount, 7) OVER (ORDER BY dt)   -- 7日前(前週の同じ曜日)の値
LAG(amount)    OVER (ORDER BY dt)   -- 第2引数省略時は1(=前日)
-- オフセットを変えるだけで「比較の基準」を昨日↔前週同曜日に切替えられる
LAG の第2引数=何行前か:LAG(col, n) は現在行からn行前の値を返します(既定 n=1)。日次データを日付順に並べれば 7行前=前週同曜日。先頭7日は遡る相手が無く NULL になるため、判定対象から外す(no_baseline)必要があります。
問題

daily_traffic(14日分・週末は高水準の週次季節性あり)から、各日の値を「7日前(前週同曜日)」と比較し、週次変化率 wow_pct =(amount − base_7d)/base_7d×100 を計算してください。7日前が無い先頭7日は 'no_baseline'、|wow_pct| が 50% を超えたら 'anomaly'、それ以外は 'normal' の status を付与。出力列は dt, amount, base_7d, wow_pct, status、dt 昇順。pct は小数第1位まで。

使用テーブル
► daily_traffic(14行)
dtamount
2024-01-01100
2024-01-02110
2024-01-03120
2024-01-04130
2024-01-05140
2024-01-06300
2024-01-07310
2024-01-08105
2024-01-09115
2024-01-10125
2024-01-11135
2024-01-12500
2024-01-13305
2024-01-14315
期待出力
dtamountbase_7dwow_pctstatus
2024-01-01100NULLNULLno_baseline
2024-01-02110NULLNULLno_baseline
2024-01-03120NULLNULLno_baseline
2024-01-04130NULLNULLno_baseline
2024-01-05140NULLNULLno_baseline
2024-01-06300NULLNULLno_baseline
2024-01-07310NULLNULLno_baseline
2024-01-081051005.0normal
2024-01-091151104.5normal
2024-01-101251204.2normal
2024-01-111351303.8normal
2024-01-12500140257.1anomaly
2024-01-133053001.7normal
2024-01-143153101.6normal
QUESTION 9

複合シグナルの重大度判定 — 水準と急変の2軸を組み合わせCASEで深刻度を分類する

複数CASE分岐真偽値の評価順複合シグナル重大度スコアリング
前提知識

実務のアラートは「異常か否か」の2値では足りません。複数のシグナルを組み合わせ、深刻度(severity)でランク分けするのが定番です。本問は2つの独立した観点——水準(値が高いか)急変(前日から急に変わったか)——を掛け合わせ、4段階に分類します。鍵は CASE分岐の順序と、真偽値(boolean)をそのまま条件に使う書き方です。

CASE
  WHEN impact_high AND urgency_high THEN 'BLOCKER'  -- 影響・緊急度とも高い
  WHEN urgency_high                 THEN 'URGENT'   -- 緊急度だけ高い
  WHEN impact_high                  THEN 'REVIEW'   -- 影響だけ高い
  ELSE 'NORMAL'
END
CASE は上から順に評価され、最初に真になった枝で確定:そのため複合条件など最も厳しい条件を一番上に置きます。単独条件を先に置くと、複合条件に該当する行が途中の枝で確定し、重大度を取りこぼします。
問題

metric_stream から、2つのシグナルを計算してください:水準シグナル lvl_hi =(amount > 200)急変シグナル jump_hi =(amount − 前日値 ≥ 100)。これらを CASE で組み合わせ、両方真→'CRITICAL'/水準のみ→'WARNING'/急変のみ→'WATCH'/どちらも偽→'OK' の severity を付与します。出力列は dt, amount, delta, severity、dt 昇順(delta = amount − 前日値)。

使用テーブル
► metric_stream(8行)
dtamount
2024-01-01100
2024-01-02105
2024-01-03215
2024-01-04220
2024-01-05120
2024-01-06235
2024-01-0790
2024-01-08195
期待出力
dtamountdeltaseverity
2024-01-01100NULLOK
2024-01-02105+5OK
2024-01-03215+110CRITICAL
2024-01-04220+5WARNING
2024-01-05120-100OK
2024-01-06235+115CRITICAL
2024-01-0790-145OK
2024-01-08195+105WATCH
QUESTION 10

アラートサマリの集約 — 多段 FILTER と GROUP BY ROLLUP で『カテゴリ別+全体』を一枚に【総まとめ】

COUNT(*) FILTER ×複数GROUP BY ROLLUP重大度別の集計サマリ+小計総まとめ
前提知識

応用編の締めは運用に渡す『アラートサマリ』の作成です。基礎編の COUNT(*) FILTER を発展させ、重大度ごとに複数の FILTER 列を並べて深刻度別件数を一度に数え、さらに GROUP BY ROLLUPカテゴリ別の明細と全体の総計を同じ結果に同居させます。1クエリでダッシュボードの一枚が完成します。

COUNT(*) FILTER (WHERE amount >= 300)                  -- critical 件数
COUNT(*) FILTER (WHERE amount >= 200 AND amount < 300)  -- warning 件数
GROUP BY ROLLUP (category)   -- カテゴリ別の各行 + 全体の総計行(category=NULL) を生成
ROLLUP は「明細+小計」を一発で:GROUP BY ROLLUP (category) は通常のカテゴリ別グループに加え、全カテゴリを束ねた総計行を自動で1行追加します。その総計行では category が NULL になるので、COALESCE(category,'(ALL)') でラベル化し、GROUPING(category) で並び順を制御します。
問題

event_log から、amount>=300 を critical、200≤amount<300 を warning と定義し、カテゴリ別に 総件数・critical件数・warning件数・critical率% を集計してください。さらに ROLLUP全カテゴリの総計行も同じ結果に含め、総計行のラベルは (ALL) とします。出力列は category, total_events, critical, warning, crit_pct、カテゴリ行を category 昇順、総計行を最後に。pct は小数第1位まで。

使用テーブル
► event_log(12行)
categorydtamount
api2024-01-01350
api2024-01-02120
api2024-01-03130
api2024-01-0495
auth2024-01-01260
auth2024-01-02280
auth2024-01-03240
auth2024-01-0490
web2024-01-01100
web2024-01-02250
web2024-01-03300
web2024-01-04320
期待出力
categorytotal_eventscriticalwarningcrit_pct
api41025.0
auth4030.0
web42150.0
(ALL)123425.0