「前年比」「前月比」「前週比」 は経営会議の定番。だが実装を間違えると クエリが急に 10 秒 → 数分 になります。本記事では 指標の意味 と 重くしない実装パターン をセットで扱います。
前年比・前月比・前週比の使い分け
| 指標 | 計算式 | 向く文脈 |
|---|---|---|
| 前年比(YoY) | 今期 / 前年同期 - 1 | 季節性が強い業種(小売・観光・製造) |
| 前月比(MoM) | 今月 / 前月 - 1 | 短期トレンド、・スタートアップ |
| 前週比(WoW) | 今週 / 先週 - 1 | 高速 、グロース・マーケ |
| 前期比 vs 前年同期比 | 選択 | 1月の前期は12月。1月の前年同期は1年前の1月。混同しない |
「前期比 +20%」と言われたとき、前月比なのか前四半期比なのか前年同期比なのか曖昧です。社内ダッシュボードでは 「前月比 +XX%」「前年同月比 +YY%」と明示しましょう。
1. 単純な実装:LAG ウィンドウ関数
SELECT month, revenue, LAG(revenue, 1) OVER (ORDER BY month) AS revenue_prev_month, LAG(revenue, 12) OVER (ORDER BY month) AS revenue_prev_year, SAFE_DIVIDE(revenue, LAG(revenue, 1) OVER (ORDER BY month)) - 1 AS mom, SAFE_DIVIDE(revenue, LAG(revenue, 12) OVER (ORDER BY month)) - 1 AS yoyFROM monthly_revenueORDER BY month;2. 大規模テーブルで使うときの落とし穴
生データテーブル(10億行・パーティショニング済み)に対して LAG をかけるのは禁忌。`OVER (ORDER BY ...)` は パーティション全体をソート するため、メモリ枯渇 / コスト爆発します。
では 直近 30 日のレポートでも、前年同期を出すために `_PARTITIONDATE >= 1 年前` を `WHERE` に書きがち。スキャン量が 12 倍になります。集計済みテーブルを使うのが正解。
3. 前年比を軽くする3つのパターン
パターン A: マテリアライズド集計テーブル
日次・月次の集計を別テーブルに永続化。レポートはそのテーブルに対してクエリするので、生データを毎回触らない。 の `incremental` モデルで実装するのが定型。
-- models/daily_kpi.sql{{ config(materialized='incremental', unique_key='date') }}
SELECT DATE(event_timestamp) AS date, COUNT(DISTINCT user_id) AS dau, SUM(amount) AS revenue, COUNT(*) AS event_countFROM {{ ref('events_partitioned') }}{% if is_incremental() %} -- 増分実行: 過去2日分だけを再計算(遅延データを許容) WHERE DATE(event_timestamp) >= (SELECT MAX(date) FROM {{ this }}) - 1{% endif %}GROUP BY dateパターン B: マテリアライズドビュー(BigQuery / )
自動更新される事前集計。BigQuery / Snowflake / で対応。 を書くだけで自動的に背後で増分更新されます。
CREATE MATERIALIZED VIEW project.dataset.daily_kpi_mv ASSELECT DATE(event_timestamp) AS date, APPROX_COUNT_DISTINCT(user_id) AS dau_approx, SUM(amount) AS revenueFROM project.dataset.events_partitionedGROUP BY date;
-- レポート側はこれを使うだけSELECT date, dau_approx, revenue, LAG(revenue, 365) OVER (ORDER BY date) AS revenue_prev_yearFROM project.dataset.daily_kpi_mvWHERE date >= CURRENT_DATE() - 30;パターン C: HyperLogLog で近似ユニーク
`COUNT(DISTINCT user_id)` は最も重い集計の1つ。 を使うと、メモリは 1/1000、誤差 ±2% 程度でユニーク数が出せます。
-- 日次 sketch を作って永続化CREATE OR REPLACE TABLE daily_user_hll ASSELECT DATE(event_timestamp) AS date, HLL_COUNT.INIT(user_id) AS user_hllFROM events_partitionedGROUP BY date;
-- 月次 ユニークユーザー(再集計)はマージするだけSELECT DATE_TRUNC(date, MONTH) AS month, HLL_COUNT.MERGE(user_hll) AS mau_approxFROM daily_user_hllGROUP BY month;MAU = 月内ユニーク を `COUNT(DISTINCT)` で出すと 30 日分の生ログをすべてスキャン が必要。HLL なら 日次 sketch を月でマージするだけで近似値が出ます。 EP.02 と組み合わせて使ってください。
4. 前年比の表現の作法(可視化サイド)
- 棒グラフ + 折れ線:絶対値(棒)と前年比(折れ線で右軸)の組み合わせは経営会議の定番
- コンディショナルカラー:前年比 +5% 超は緑、-5% 未満は赤、その間はグレーで強調
- ±0% の基準線を太く:前年比は「ゼロを跨ぐかどうか」が最重要
- 前年「同期」を明示:「前年同月」と「先月」の混同を避けるため、ラベルに `(YoY)` `(MoM)` を付ける
5. 重くしないための設計チェックリスト
- 生ログに対する `COUNT(DISTINCT)` を直接書かない:日次集計テーブルかマテビュー経由
- ダッシュボードのクエリは集計テーブルから: ツールから生ログにアクセスさせない
- LAG/LEAD の OVER 句にパーティション範囲を入れる:`PARTITION BY year` などで処理範囲を絞る
- APPROX_COUNT_DISTINCT / HLL を活用:精度要求が低いところは積極的に近似
- dbt の incremental モデル:日次バッチで集計を更新、レポート時は触らない
次回以降の予告(業界別ダッシュボード集)
EP.23 以降は 業界別ダッシュボード集:小売・製造・医療・物流・スタートアップ。それぞれの業界で「この指標を最初に見る」という定番テンプレを順番に紹介します。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。