ふくふくHukuhuku Inc.
EP.22Viz 13分公開: 2026-05-10

前年比・前月比の使い所と、巨大テーブルでもクエリを重くしない設計

「前年比」は経営層が最も信頼する指標。だが計算は重くなりがち。LAG ウィンドウ関数の正しい使い方、マテリアライズドビュー、近似ユニーク(HyperLogLog)でダッシュボードを軽くする実装パターン。

#前年比#YoY#bigquery#クエリ最適化
シェア

「前年比」「前月比」「前週比」 は経営会議の定番。だが実装を間違えると クエリが急に 10 秒 → 数分 になります。本記事では 指標の意味重くしない実装パターン をセットで扱います。

前年比・前月比・前週比の使い分け

指標計算式向く文脈
前年比(YoY)今期 / 前年同期 - 1季節性が強い業種(小売・観光・製造)
前月比(MoM)今月 / 前月 - 1短期トレンド、・スタートアップ
前週比(WoW)今週 / 先週 - 1高速 、グロース・マーケ
前期比 vs 前年同期比選択1月の前期は12月。1月の前年同期は1年前の1月。混同しない
「前期比」は誤解を招く

「前期比 +20%」と言われたとき、前月比なのか前四半期比なのか前年同期比なのか曖昧です。社内ダッシュボードでは 「前月比 +XX%」「前年同月比 +YY%」と明示しましょう。

1. 単純な実装:LAG ウィンドウ関数

前月比を LAG で計算(小規模ならこれで十分)
SQL
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 ...)` は パーティション全体をソート するため、メモリ枯渇 / コスト爆発します。

「前年同期」で過去 1 年分を毎回スキャンしない

では 直近 30 日のレポートでも、前年同期を出すために `_PARTITIONDATE >= 1 年前` を `WHERE` に書きがちスキャン量が 12 倍になります。集計済みテーブルを使うのが正解。

3. 前年比を軽くする3つのパターン

パターン A: マテリアライズド集計テーブル

日次・月次の集計を別テーブルに永続化。レポートはそのテーブルに対してクエリするので、生データを毎回触らない の `incremental` モデルで実装するのが定型。

dbt incremental で日次集計を作る
SQL
-- 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 / で対応。 を書くだけで自動的に背後で増分更新されます。

BigQuery のマテリアライズドビュー
SQL
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% 程度でユニーク数が出せます。

BigQuery の HLL_COUNT.MERGE で日次→月次に集約
SQL
-- 日次 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;
は HLL の真骨頂

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 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp