前月比と店舗比較 — 縦の並びを横に変える

上級153

LAG で前月比を出し、条件付き集計で店舗を横に並べます。全体の数字だけを見ていると読み違える例を、実際のデータで確かめます。

前月比 — LAG で 1 つ前の行を持ってくる

月次に畳んでから LAG で 1 行前の値を取り、 差や比を計算します。

WITH monthly AS (
  SELECT strftime('%Y-%m', sale_date) AS month, SUM(amount) AS sales
  FROM daily_sales
  GROUP BY month
)
SELECT month, sales,
       LAG(sales) OVER (ORDER BY month) AS prev,
       ROUND((sales - LAG(sales) OVER (ORDER BY month)) * 100.0
             / LAG(sales) OVER (ORDER BY month), 1) AS mom
FROM monthly
ORDER BY month;

全体では 2 月が +9.8%、 3 月が +24.6% と伸びています。

最初の月は比べる相手がいないので prevmom も NULL です。 これは

エラーではなく正しい状態なので、 表示するときは 「—」 に置き換えるなどします。

COALESCE(CAST(mom AS TEXT), '—')

LAG を 3 回書くのが気になるとき

同じ LAG(sales) OVER (ORDER BY month) を 3 回書いていますが、

WITH でもう一段包めば 1 回で済みます

WITH monthly AS (...),
     with_prev AS (
       SELECT month, sales, LAG(sales) OVER (ORDER BY month) AS prev FROM monthly
     )
SELECT month, sales, prev,
       ROUND((sales - prev) * 100.0 / prev, 1) AS mom
FROM with_prev
ORDER BY month;

読みやすさが変わるだけで結果は同じです。 式が 2 回以上出てきたら

WITH で名前を付けることを考えます。

縦を横にする — 条件付き集計

店舗を並べて比べたいとき、 GROUP BY だと縦に 6 行並びます。

月ごとに 1 行、 店舗を列にしたいときは SUM(CASE WHEN ...) を使います。

SELECT strftime('%Y-%m', sale_date) AS month,
       SUM(CASE WHEN store = '東京本店' THEN amount ELSE 0 END) AS tokyo,
       SUM(CASE WHEN store = '大阪支店' THEN amount ELSE 0 END) AS osaka
FROM daily_sales
GROUP BY month
ORDER BY month;

条件に合う行だけ金額を、 合わない行は 0 を足す — これで 1 列が 1 店舗になります。

表計算のピボットテーブルと同じことを SQL でやっている形です。

列は書いた分しか増えません。 店舗が増えたら SQL を書き直す必要があります。

店舗数が可変なら、 縦のまま出してアプリ側で横に組み替えるほうが素直です。

差と比の両方を出す

SUM(CASE WHEN store = '東京本店' THEN amount ELSE 0 END)
- SUM(CASE WHEN store = '大阪支店' THEN amount ELSE 0 END) AS diff

差は 1 月 27.9 万 → 3 月 195.1 万と、 3 月に急に開いています。

全体だけ見ていると読み違える

全体の前月比は 3 月 +24.6% でした。 ところが店舗別に PARTITION BY store

足して同じことをすると、 まったく違う絵が出ます。

LAG(sales) OVER (PARTITION BY store ORDER BY month)
店舗2 月3 月
東京本店+8.3%+47.3%
大阪支店+11.5%+0.5%

3 月の伸びはほぼ全部が東京本店で、 大阪支店は横ばいでした。

全体の +24.6% だけを見て 「両店とも好調」 と報告すると誤りになります。

PARTITION BY を足すかどうかで結論が変わる — これが分析クエリで

いちばん怖いところで、 同時にいちばん価値のあるところです。

よくあるつまずき

- PARTITION BY を書き忘れて前月比がぐちゃぐちゃになる — 店舗が混ざった並びで

1 つ前の行を取るので、 東京の 2 月と大阪の 1 月を比べたりします。

ORDER BY だけでなく PARTITION BY も必ずセットで考えます

- NULL に演算して全部 NULL になる — 最初の月は prev が NULL なので、

引き算も割り算も NULL です。 それが正しい挙動です。

- CASE の ELSE を 0 でなく NULL にするSUM は NULL を無視するので

合計は同じですが、 該当行が 1 つも無い月は 0 ではなく NULL になります。

どちらが欲しいかで選びます。

実務での使いどころ

前月比と店舗比較は、 月次会議の資料そのものです。 「全体は伸びた」 で終わらせず、

必ず 1 段分解して確かめる。 SQL では PARTITION BY を 1 つ足すだけで済みます。

データベースを初期化中...
SQL エディタCtrl+Enter で実行
SQL を入力して実行してください
練習問題 — 0/3 完了 (0/100pt)
月次売上に前月の値 prev と前月比 mom (%、小数第 1 位) を足してください。列は month・sales・prev・mom、月の昇順です。
30pt
月ごとに 1 行で、東京本店の売上 tokyo・大阪支店の売上 osaka・その差 diff (東京 − 大阪) を出してください。列は month・tokyo・osaka・diff、月の昇順です。
30pt
店舗ごとの月次売上に、その店舗の中での前月比 mom を足してください。列は month・store・sales・mom、store 昇順 → month 昇順で並べます。
40pt