LAG で前月比を出し、条件付き集計で店舗を横に並べます。全体の数字だけを見ていると読み違える例を、実際のデータで確かめます。
月次に畳んでから 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% と伸びています。
最初の月は比べる相手がいないので prev も mom も NULL です。 これは
エラーではなく正しい状態なので、 表示するときは 「—」 に置き換えるなどします。
COALESCE(CAST(mom AS TEXT), '—')
同じ 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 つ足すだけで済みます。