Step 9:LAG、LEAD、FIRST_VALUE、LAST_VALUE

↔️ Step 9: LAG、LEAD、FIRST_VALUE、LAST_VALUE

前後の行を参照して比較しよう!

📋 このステップで学ぶこと
  • LAG関数:前の行を参照する方法
  • LEAD関数:次の行を参照する方法
  • 前月比・前年同月比の計算
  • FIRST_VALUE:最初の値を取得
  • LAST_VALUE:最後の値を取得
  • 購入間隔・成長率の計算
  • 4つの関数の組み合わせ

🎯 1. LAG関数とは?

1-1. 前の行のデータを取得する

LAG関数は、現在の行から前の行のデータを取得できるウィンドウ関数です。「前月の売上」「前回の購入日」など、過去との比較に非常に便利です。

💡 LAGのイメージ(後ろを振り返る)

「LAG」は英語で「遅れ」という意味です。現在の行から「遅れた位置」= 前の行を見ることができます。

売上 LAG(売上, 1)
1 1月 100万 NULL(前がない)
2 2月 120万 100万(← 1月の値)
3 3月 150万 120万(← 2月の値)
4 4月 180万 150万(← 3月の値)

各行に「1つ前の行の売上」が表示されます。これで前月売上との比較が簡単にできます!

1-2. LAG関数の基本構文

📌 LAG関数の書き方

※横にスクロールできます

LAG(取得する列, 何行前か) OVER (ORDER BY 並び順の列)

各部分の意味:

  • 取得する列 → 前の行から取得したいデータの列
  • 何行前か → 何行前を参照するか(省略すると1)
  • ORDER BY → どの順番で「前」を決めるか

1-3. 例1:前月の売上を取得する

やりたいこと:月ごとの売上と、前月の売上を並べて表示する

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_month_sales FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;
クエリの解説:
  • strftime('%Y-%m', order_date) → 日付を「年-月」形式に変換
  • SUM(total_amount) → 月ごとの売上合計
  • LAG(SUM(total_amount), 1) → 1行前(前月)の売上を取得
  • ORDER BY strftime('%Y-%m', order_date) → 年月順に並べて「前」を決定
結果イメージ:
year_month monthly_sales prev_month_sales
2024-01 1,000,000 NULL(前月なし)
2024-02 1,200,000 1,000,000
2024-03 1,500,000 1,200,000
2024-04 1,800,000 1,500,000

最初の行(1月)は前月がないのでNULLになります。これは正常な動作です。

📈 2. 前月比の計算

2-1. 前月比とは

前月比は、「今月の売上が前月と比べてどれだけ増減したか」を示す指標です。ビジネスでは非常によく使われます。

前月比の計算式:
  • 増減額 = 今月 – 前月
  • 増減率(%) = (今月 – 前月) ÷ 前月 × 100

2-2. 前月比(増減額と増減率)を計算する

やりたいこと:月次売上の前月比(増減額と増減率)を計算する

コードを段階的に見ていきましょう。

ステップ1:今月売上と前月売上を並べる

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_month_sales FROM orders GROUP BY strftime(‘%Y-%m’, order_date)

ステップ2:増減額を計算

今月売上 – 前月売上 で増減額を求めます。

※横にスクロールできます

SUM(total_amount) – LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS diff_amount

ステップ3:増減率を計算

(今月 – 前月) ÷ 前月 × 100 で増減率を求めます。

※横にスクロールできます

ROUND( (SUM(total_amount) – LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) )) * 100.0 / LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ), 2 ) AS growth_rate
計算式の解説:
  • * 100.0 → パーセントに変換(.0をつけて小数計算にする)
  • / LAG(...) → 前月売上で割る
  • ROUND(..., 2) → 小数点以下2桁に丸める

完成コード:

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_month_sales, SUM(total_amount) – LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS diff_amount, ROUND( (SUM(total_amount) – LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) )) * 100.0 / LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ), 2 ) AS growth_rate FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;
結果イメージ:
year_month monthly_sales prev_month_sales diff_amount growth_rate
2024-01 1,000,000 NULL NULL NULL
2024-02 1,200,000 1,000,000 200,000 20.00
2024-03 1,500,000 1,200,000 300,000 25.00
2024-04 1,800,000 1,500,000 300,000 20.00

2月は前月比+20%、3月は+25%と、成長率がわかります。

📅 3. 前年同月比の計算

3-1. 前年同月比とは

前年同月比は、「去年の同じ月と比べてどれだけ増減したか」を示す指標です。季節変動の影響を取り除いて、純粋な成長を測ることができます。

💡 なぜ前年同月比を使うのか

例えば、お正月商戦で1月の売上が高い小売業の場合、「1月 vs 2月」で比較すると必ず下がります。でも「今年1月 vs 去年1月」なら、季節要因を除いた成長が分かります。

3-2. LAGで12ヶ月前を参照する

やりたいこと:月次売上の前年同月比を計算する

月次データの場合、12行前 = 12ヶ月前 = 前年同月になります。

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, LAG(SUM(total_amount), 12) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_year_sales, ROUND( (SUM(total_amount) – LAG(SUM(total_amount), 12) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) )) * 100.0 / LAG(SUM(total_amount), 12) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ), 2 ) AS yoy_growth FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;
クエリの解説:
  • LAG(SUM(total_amount), 12)12行前(12ヶ月前)の売上を取得
  • yoy_growth → Year over Year(前年比)の略でよく使われる名前
結果イメージ(2024年のデータ):
year_month monthly_sales prev_year_sales yoy_growth
2024-01 1,000,000 800,000(2023年1月) 25.00
2024-02 1,200,000 1,000,000(2023年2月) 20.00
2024-03 1,500,000 1,200,000(2023年3月) 25.00

最初の12ヶ月間は前年データがないのでNULLになります。

3-3. LAGの第2引数のまとめ

📌 LAGの第2引数(何行前か)
データの単位 LAGの指定 意味
月次データ LAG(列, 1) 前月
月次データ LAG(列, 12) 前年同月
週次データ LAG(列, 1) 先週
週次データ LAG(列, 52) 前年同週
日次データ LAG(列, 1) 前日
日次データ LAG(列, 7) 先週の同じ曜日
日次データ LAG(列, 365) 前年同日

🔄 4. PARTITION BYとの組み合わせ

4-1. グループごとに前の行を参照する

やりたいこと:カテゴリごとに、前月の売上を取得する

PARTITION BYと組み合わせることで、グループごとに「前の行」を参照できます。

※横にスクロールできます

SELECT category, strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, LAG(SUM(total_amount), 1) OVER ( PARTITION BY category ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_month_sales FROM orders INNER JOIN products ON orders.product_id = products.product_id GROUP BY category, strftime(‘%Y-%m’, order_date) ORDER BY category, year_month;
クエリの解説:
  • PARTITION BY category → カテゴリごとに区切る
  • 各カテゴリ内で「前の行」を参照
  • カテゴリが変わると、前月売上はNULLにリセット
結果イメージ:
category year_month monthly_sales prev_month_sales
果物 2024-01 300,000 NULL(リセット)
果物 2024-02 350,000 300,000
野菜 2024-01 400,000 NULL(リセット)
野菜 2024-02 420,000 400,000

カテゴリが変わると前月売上がNULLになります。「果物カテゴリの前」は「果物カテゴリの前月」であり、野菜カテゴリのデータは参照しません。

活用シーン:
  • カテゴリ別成長率:各カテゴリの前月比を計算
  • 店舗別売上比較:各店舗の前月比を計算
  • 顧客別購入履歴:各顧客の前回購入を取得

➡️ 5. LEAD関数 – 次の行を参照

5-1. LEAD関数とは

LEAD関数は、現在の行から次の行のデータを取得できるウィンドウ関数です。LAGの逆で、「未来」を見ることができます。

💡 LEADのイメージ(先を見る)

「LEAD」は英語で「先導する」という意味です。現在の行より「先」= 次の行を見ることができます。

売上 LEAD(売上, 1)
1 1月 100万 120万(→ 2月の値)
2 2月 120万 150万(→ 3月の値)
3 3月 150万 180万(→ 4月の値)
4 4月 180万 NULL(次がない)

各行に「1つ後の行の売上」が表示されます。LAGとは逆方向です!

5-2. LEAD関数の基本構文

📌 LEAD関数の書き方

※横にスクロールできます

LEAD(取得する列, 何行後か) OVER (ORDER BY 並び順の列)

LAGと同じ構文ですが、参照方向が逆(次の行)になります。

5-3. 例1:翌月の売上を取得

やりたいこと:月ごとの売上と、翌月の売上を並べて表示する

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, LEAD(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS next_month_sales FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;
結果イメージ:
year_month monthly_sales next_month_sales
2024-01 1,000,000 1,200,000
2024-02 1,200,000 1,500,000
2024-03 1,500,000 1,800,000
2024-04 1,800,000 NULL(翌月なし)

最後の行(4月)は翌月データがないのでNULLになります。

5-4. 例2:前月・今月・翌月を同時に表示

やりたいこと:LAGとLEADを同時に使って、前月・今月・翌月を並べる

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_month, SUM(total_amount) AS current_month, LEAD(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS next_month FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;
クエリの解説:
  • LAG(..., 1) → 1行前(前月)
  • SUM(...) → 今月(現在の行)
  • LEAD(..., 1) → 1行後(翌月)
ポイント:

LAGとLEADは同時に使えます。これで前後を一度に比較できます。

結果イメージ:
year_month prev_month current_month next_month
2024-01 NULL 1,000,000 1,200,000
2024-02 1,000,000 1,200,000 1,500,000
2024-03 1,200,000 1,500,000 1,800,000
2024-04 1,500,000 1,800,000 NULL

5-5. 例3:次回購入までの日数を計算

やりたいこと:顧客の注文履歴から、次の購入日と次回までの日数を計算する

※横にスクロールできます

SELECT c.customer_name, o.order_date, o.total_amount, LEAD(o.order_date, 1) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date ) AS next_order_date, julianday(LEAD(o.order_date, 1) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date )) – julianday(o.order_date) AS days_to_next FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id ORDER BY c.customer_name, o.order_date;
クエリの解説:
  • PARTITION BY c.customer_id → 顧客ごとに区切る
  • LEAD(o.order_date, 1) → 同じ顧客の次の注文日を取得
  • julianday() → 日付をユリウス日(日数)に変換(SQLite)
  • 次の注文日 – 今の注文日 = 次回までの日数
📌 LAGとLEADの使い分け
関数 参照方向 主な用途
LAG 前(過去) 前月比、成長率、前回からの変化
LEAD 後(未来) 次回予測、目標との比較、購入間隔

実務ではLAGの方がよく使います。過去との比較(前月比、成長率)が多いためです。

🥇 6. FIRST_VALUE – 最初の値を取得

6-1. FIRST_VALUE関数とは

FIRST_VALUE関数は、ウィンドウ内の最初の行の値を取得します。「初回購入額」「年初の売上」「カテゴリ内の最安値」などに使います。

💡 FIRST_VALUEのイメージ

「最初の値」を全ての行に表示できます。

売上 FIRST_VALUE(売上)
1 1月 100万 100万(最初)
2 2月 120万 100万(最初)
3 3月 150万 100万(最初)
4 4月 180万 100万(最初)

全ての行に「1月の売上(最初の値)」が表示されます。これで「年初からの成長率」が計算できます!

6-2. FIRST_VALUE関数の基本構文

📌 FIRST_VALUE関数の書き方

※横にスクロールできます

FIRST_VALUE(取得する列) OVER ( ORDER BY 並び順の列 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )

ポイント: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW を指定すると、「最初から現在まで」の範囲で最初の値を取得します。

6-3. 例1:カテゴリ内の最安値を表示

やりたいこと:各商品に、そのカテゴリ内の最安値を表示する

※横にスクロールできます

SELECT product_name, category, price, FIRST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS min_price_in_category FROM products ORDER BY category, price;
クエリの解説:
  • PARTITION BY category → カテゴリごとに区切る
  • ORDER BY price → 価格の安い順に並べる
  • FIRST_VALUE(price) → 最初(最も安い)の価格を取得
なぜ最安値になるか:

ORDER BY priceで価格の安い順に並べているので、「最初の値」= 「最も安い価格」になります。

結果イメージ:
product_name category price min_price_in_category
バナナ 果物 120 120
りんご 果物 150 120
みかん 果物 180 120
ぶどう 果物 500 120

果物カテゴリの全商品に、最安値の120円が表示されます。

6-4. 例2:最安値との差額を計算

やりたいこと:各商品の価格と、カテゴリ最安値との差額を計算する

※横にスクロールできます

SELECT product_name, category, price, FIRST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS min_price, price – FIRST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS diff_from_min FROM products ORDER BY category, price;
結果イメージ:
product_name category price min_price diff_from_min
バナナ 果物 120 120 0
りんご 果物 150 120 30
ぶどう 果物 500 120 380

6-5. 例3:年初からの成長率を計算

やりたいこと:月次売上と年初の売上を比較し、年初からの成長率を計算する

コードを段階的に見ていきましょう。

ステップ1:年ごとに区切って年初売上を取得

※横にスクロールできます

FIRST_VALUE(SUM(total_amount)) OVER ( PARTITION BY strftime(‘%Y’, order_date) ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS jan_sales
ポイント:
  • PARTITION BY strftime('%Y', order_date) → 年ごとに区切る
  • 2024年1月〜12月は2024年グループ、2025年1月〜12月は2025年グループ
  • 各グループの「最初の値」= 年初(1月)の売上

ステップ2:年初比(成長率)を計算

※横にスクロールできます

ROUND( (SUM(total_amount) – FIRST_VALUE(SUM(total_amount)) OVER (…)) * 100.0 / FIRST_VALUE(SUM(total_amount)) OVER (…), 2 ) AS ytd_growth

完成コード:

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, FIRST_VALUE(SUM(total_amount)) OVER ( PARTITION BY strftime(‘%Y’, order_date) ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS jan_sales, ROUND( (SUM(total_amount) – FIRST_VALUE(SUM(total_amount)) OVER ( PARTITION BY strftime(‘%Y’, order_date) ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )) * 100.0 / FIRST_VALUE(SUM(total_amount)) OVER ( PARTITION BY strftime(‘%Y’, order_date) ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ), 2 ) AS ytd_growth FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;
結果イメージ:
year_month monthly_sales jan_sales ytd_growth
2024-01 1,000,000 1,000,000 0.00
2024-02 1,200,000 1,000,000 20.00
2024-03 1,500,000 1,000,000 50.00
2024-06 2,000,000 1,000,000 100.00

6月は年初(1月)の2倍になっているので、年初比+100%です。

6-6. 例4:顧客の初回購入額との比較

やりたいこと:顧客の各注文に、初回購入額と初回からの変化を表示する

※横にスクロールできます

SELECT c.customer_name, o.order_date, o.total_amount, FIRST_VALUE(o.total_amount) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS first_purchase, o.total_amount – FIRST_VALUE(o.total_amount) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS diff_from_first FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id ORDER BY c.customer_name, o.order_date;
活用シーン:
  • 顧客育成の分析:初回購入から購入額が増えているか
  • 優良顧客の発見:初回からの成長率が高い顧客
  • 離脱予測:初回より購入額が減っている顧客

🏁 7. LAST_VALUE – 最後の値を取得

7-1. LAST_VALUE関数とは

LAST_VALUE関数は、ウィンドウ内の最後の行の値を取得します。「最終購入額」「年末の売上」「カテゴリ内の最高値」などに使います。

⚠️ LAST_VALUEの重要な注意点

LAST_VALUEはフレーム指定が非常に重要です!

フレーム指定なしの場合、デフォルトで「最初から現在の行まで」が対象になります。つまり、「最後の値」= 「現在の行の値」になってしまい、意味がありません。

必ず ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING を指定して、「最初から最後まで」を対象にしましょう。

7-2. フレーム指定の違いを理解する

📌 LAST_VALUEのフレーム指定
フレーム指定 意味 結果
指定なし(デフォルト) 最初から現在まで ❌ 現在の行が「最後」になる
UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING 最初から最後まで ✅ 本当の「最後の値」が取れる

7-3. LAST_VALUE関数の正しい構文

📌 LAST_VALUE関数の書き方(正しい方法)

※横にスクロールできます

LAST_VALUE(取得する列) OVER ( ORDER BY 並び順の列 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING )

UNBOUNDED FOLLOWING(最後まで)を必ず指定しましょう!

7-4. 例1:カテゴリ内の最高値を表示

やりたいこと:各商品に、そのカテゴリ内の最高値を表示する

※横にスクロールできます

SELECT product_name, category, price, LAST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS max_price_in_category FROM products ORDER BY category, price;
クエリの解説:
  • ORDER BY price → 価格の安い順に並べる
  • LAST_VALUE(price) → 最後(最も高い)の価格を取得
  • UNBOUNDED FOLLOWING → 最後の行まで見る(これが重要!)
結果イメージ:
product_name category price max_price_in_category
バナナ 果物 120 500
りんご 果物 150 500
みかん 果物 180 500
ぶどう 果物 500 500

果物カテゴリの全商品に、最高値の500円が表示されます。

7-5. 例2:最高値との差額を計算

やりたいこと:各商品の価格と、カテゴリ最高値との差額を計算する

※横にスクロールできます

SELECT product_name, category, price, LAST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS max_price, LAST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) – price AS diff_from_max FROM products ORDER BY category, price;
結果イメージ:
product_name category price max_price diff_from_max
バナナ 果物 120 500 380
りんご 果物 150 500 350
ぶどう 果物 500 500 0

7-6. 例3:顧客の最終購入額を表示

やりたいこと:顧客の各注文に、最終購入額を表示する

※横にスクロールできます

SELECT c.customer_name, o.order_date, o.total_amount, LAST_VALUE(o.total_amount) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS last_purchase FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id ORDER BY c.customer_name, o.order_date;
活用シーン:
  • 顧客の現状把握:最終購入額を全履歴に表示
  • 離脱分析:最終購入額が低い顧客の特徴を分析
  • 年末目標との比較:年末の売上を全月に表示して進捗を確認

📊 8. 4つの関数の組み合わせ

8-1. 前後と最初・最後を同時に取得

やりたいこと:月次売上に、前月・翌月・初月・最終月を全て表示する

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, — LAG: 前月 LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_month, — LEAD: 翌月 LEAD(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS next_month, — FIRST_VALUE: 初月 FIRST_VALUE(SUM(total_amount)) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS first_month, — LAST_VALUE: 最終月 LAST_VALUE(SUM(total_amount)) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS last_month FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;
クエリの解説:
  • LAG(..., 1) → 1行前(前月)
  • LEAD(..., 1) → 1行後(翌月)
  • FIRST_VALUE → 最初の行(初月)
  • LAST_VALUE → 最後の行(最終月)
ポイント:

4つの関数を同時に使うことで、様々な角度から比較できます。

8-2. 商品のカテゴリ内位置づけ分析

やりたいこと:各商品に、カテゴリ内の最安商品名・価格、最高商品名・価格を表示する

※横にスクロールできます

SELECT product_name, category, price, — 最安商品名 FIRST_VALUE(product_name) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS cheapest_product, — 最安値 FIRST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS min_price, — 最高商品名 LAST_VALUE(product_name) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS most_expensive_product, — 最高値 LAST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS max_price FROM products ORDER BY category, price;
結果イメージ:
product_name category price cheapest most_expensive
バナナ 果物 120 バナナ(120円) ぶどう(500円)
りんご 果物 150 バナナ(120円) ぶどう(500円)
ぶどう 果物 500 バナナ(120円) ぶどう(500円)

💼 9. 実務での活用例

9-1. 売上トレンド分析(前月比・前年同月比・年初比)

やりたいこと:月次売上に、3つの成長率指標を同時に表示する

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, — 前月比 ROUND( (SUM(total_amount) – LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) )) * 100.0 / LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ), 2 ) AS mom_growth, — 前年同月比 ROUND( (SUM(total_amount) – LAG(SUM(total_amount), 12) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) )) * 100.0 / LAG(SUM(total_amount), 12) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ), 2 ) AS yoy_growth, — 年初比 ROUND( (SUM(total_amount) – FIRST_VALUE(SUM(total_amount)) OVER ( PARTITION BY strftime(‘%Y’, order_date) ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )) * 100.0 / FIRST_VALUE(SUM(total_amount)) OVER ( PARTITION BY strftime(‘%Y’, order_date) ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ), 2 ) AS ytd_growth FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;
3つの成長率指標:
  • mom_growth(Month over Month)→ 前月比
  • yoy_growth(Year over Year)→ 前年同月比
  • ytd_growth(Year to Date)→ 年初比

9-2. 顧客の購買行動分析

やりたいこと:顧客ごとに、初回購入額・前回からの変化・購入間隔を分析する

※横にスクロールできます

SELECT c.customer_name, o.order_date, o.total_amount, — 初回購入額 FIRST_VALUE(o.total_amount) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS first_purchase, — 初回からの変化 o.total_amount – FIRST_VALUE(o.total_amount) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS diff_from_first, — 前回購入日 LAG(o.order_date, 1) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date ) AS prev_order_date, — 前回からの日数 julianday(o.order_date) – julianday(LAG(o.order_date, 1) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date )) AS days_since_prev FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id ORDER BY c.customer_name, o.order_date;
活用シーン:
  • 顧客育成:初回購入から購入額が成長しているか確認
  • 離脱予防:購入間隔が長くなっている顧客をアラート
  • 優良顧客発見:初回から継続的に購入額が増えている顧客

9-3. 在庫変動の監視

やりたいこと:在庫の増減を追跡し、入庫・出庫を判定する

※横にスクロールできます

SELECT product_name, check_date, stock, LAG(stock, 1) OVER ( PARTITION BY product_name ORDER BY check_date ) AS prev_stock, stock – LAG(stock, 1) OVER ( PARTITION BY product_name ORDER BY check_date ) AS stock_change, CASE WHEN stock – LAG(stock, 1) OVER ( PARTITION BY product_name ORDER BY check_date ) > 0 THEN ‘入庫’ WHEN stock – LAG(stock, 1) OVER ( PARTITION BY product_name ORDER BY check_date ) < 0 THEN '出庫' ELSE '変化なし' END AS change_type FROM stock_history ORDER BY product_name, check_date;
クエリの解説:
  • LAG(stock, 1) → 前日の在庫数を取得
  • stock - LAG(...) → 今日 – 前日 = 在庫変動
  • CASE式 → 変動がプラスなら「入庫」、マイナスなら「出庫」
💡 LAG/LEADとCASE式の組み合わせ

LAGやLEADの結果をCASE式と組み合わせることで、「増減の判定」「アラート条件」などを作れます。実務で非常によく使うパターンです。

📝 練習問題

LAG、LEAD、FIRST_VALUE、LAST_VALUEをマスターしましょう!

問題 1基本

前月売上の表示

月ごとの売上と前月売上を表示してください。

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_month_sales FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;

解説:

  • LAG(列, 1) で1行前(前月)のデータを取得
  • ORDER BY で年月順に並べることで「前の行」= 「前月」になる

問題 2基本

前月比の計算

月次売上の前月比(増減率)を計算してください。

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_month_sales, ROUND( (SUM(total_amount) – LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) )) * 100.0 / LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ), 2 ) AS mom_growth FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;

解説:

  • 計算式: (今月 – 前月) ÷ 前月 × 100
  • * 100.0 で小数計算にしてパーセントに変換

問題 3基本

翌月売上の表示

月ごとの売上と翌月売上を表示してください。

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, LEAD(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS next_month_sales FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;

解説:

  • LEAD(列, 1) で1行後(翌月)のデータを取得
  • LAGの逆方向(未来)を参照

問題 4応用

前年同月比

月次売上の前年同月比を計算してください。

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, LAG(SUM(total_amount), 12) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_year_sales, ROUND( (SUM(total_amount) – LAG(SUM(total_amount), 12) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) )) * 100.0 / LAG(SUM(total_amount), 12) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ), 2 ) AS yoy_growth FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;

解説:

  • LAG(列, 12) で12行前(12ヶ月前 = 前年同月)を取得
  • 季節変動の影響を除いた成長率を計算できる

問題 5応用

前月・翌月の同時表示

月次売上に、前月売上と翌月売上を同時に表示してください。

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_month, SUM(total_amount) AS current_month, LEAD(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS next_month FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;

解説:

  • LAGとLEADは同時に使える
  • 前後を一度に比較できて便利

問題 6応用

カテゴリ最安値との比較

各商品の価格と、そのカテゴリの最安値を表示してください。(FIRST_VALUEを使用)

※横にスクロールできます

SELECT product_name, category, price, FIRST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS min_price_in_category FROM products ORDER BY category, price;

解説:

  • ORDER BY priceで価格順に並べる
  • FIRST_VALUEで最初(最も安い)の価格を取得

問題 7応用

カテゴリ最高値との比較

各商品の価格と、そのカテゴリの最高値を表示してください。(LAST_VALUEを使用)

※横にスクロールできます

SELECT product_name, category, price, LAST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS max_price_in_category FROM products ORDER BY category, price;

解説:

  • LAST_VALUEは UNBOUNDED FOLLOWING が必須!
  • これがないと「現在の行」が「最後」になってしまう

問題 8チャレンジ

顧客の購入間隔

顧客ごとの注文履歴に、前回購入日と前回からの日数を表示してください。

※横にスクロールできます

SELECT c.customer_name, o.order_date, LAG(o.order_date, 1) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date ) AS prev_order_date, julianday(o.order_date) – julianday(LAG(o.order_date, 1) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date )) AS days_since_prev FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id ORDER BY c.customer_name, o.order_date LIMIT 20;

解説:

  • PARTITION BY customer_idで顧客ごとに区切る
  • julianday()で日付を日数に変換して引き算

問題 9チャレンジ

初回購入額との比較

顧客ごとの注文履歴に、初回購入額と初回からの変化額を表示してください。

※横にスクロールできます

SELECT c.customer_name, o.order_date, o.total_amount, FIRST_VALUE(o.total_amount) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS first_purchase, o.total_amount – FIRST_VALUE(o.total_amount) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS diff_from_first FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id ORDER BY c.customer_name, o.order_date LIMIT 20;

解説:

  • FIRST_VALUEで各顧客の初回購入額を全行に表示
  • 今回 – 初回 で変化額を計算

問題 10チャレンジ

年初比の計算

月次売上と年初の売上を比較し、年初からの成長率を計算してください。

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, FIRST_VALUE(SUM(total_amount)) OVER ( PARTITION BY strftime(‘%Y’, order_date) ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS jan_sales, ROUND( (SUM(total_amount) – FIRST_VALUE(SUM(total_amount)) OVER ( PARTITION BY strftime(‘%Y’, order_date) ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )) * 100.0 / FIRST_VALUE(SUM(total_amount)) OVER ( PARTITION BY strftime(‘%Y’, order_date) ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ), 2 ) AS ytd_growth FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;

解説:

  • PARTITION BY 年で年ごとに区切る
  • FIRST_VALUEで各年の1月(年初)を取得

問題 11チャレンジ

前後と最初最後の同時表示

月次売上に、前月、翌月、初月、最終月の全てを表示してください。

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS prev_month, LEAD(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ) AS next_month, FIRST_VALUE(SUM(total_amount)) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS first_month, LAST_VALUE(SUM(total_amount)) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS last_month FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;

解説:

  • 4つの関数を全て使った総合的なクエリ
  • LAST_VALUEには UNBOUNDED FOLLOWING を忘れずに!

問題 12チャレンジ

次回購入までの日数

顧客の注文履歴から、次回購入日と次回までの予想日数を計算してください。

※横にスクロールできます

SELECT c.customer_name, o.order_date, LEAD(o.order_date, 1) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date ) AS next_order_date, julianday(LEAD(o.order_date, 1) OVER ( PARTITION BY c.customer_id ORDER BY o.order_date )) – julianday(o.order_date) AS days_to_next FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id ORDER BY c.customer_name, o.order_date LIMIT 20;

解説:

  • LEADで次の購入日を取得
  • 次 – 今 で次回までの日数を計算

問題 13チャレンジ

カテゴリ内の最安・最高

各商品に、カテゴリ内の最安商品名・最安値、最高商品名・最高値を表示してください。

※横にスクロールできます

SELECT product_name, category, price, FIRST_VALUE(product_name) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS cheapest_product, FIRST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS min_price, LAST_VALUE(product_name) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS expensive_product, LAST_VALUE(price) OVER ( PARTITION BY category ORDER BY price ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS max_price FROM products ORDER BY category, price;

解説:

  • FIRST_VALUEとLAST_VALUEで商品名と価格の両方を取得
  • 商品の位置づけを詳細に分析できる

問題 14チャレンジ

在庫変動の追跡

在庫履歴から、前日在庫、在庫変動、変動タイプ(入庫/出庫/変化なし)を計算してください。

※横にスクロールできます

SELECT product_name, check_date, stock, LAG(stock, 1) OVER ( PARTITION BY product_name ORDER BY check_date ) AS prev_stock, stock – LAG(stock, 1) OVER ( PARTITION BY product_name ORDER BY check_date ) AS stock_change, CASE WHEN stock – LAG(stock, 1) OVER ( PARTITION BY product_name ORDER BY check_date ) > 0 THEN ‘入庫’ WHEN stock – LAG(stock, 1) OVER ( PARTITION BY product_name ORDER BY check_date ) < 0 THEN '出庫' ELSE '変化なし' END AS change_type FROM stock_history ORDER BY product_name, check_date;

解説:

  • LAGとCASE式を組み合わせて変動タイプを判定
  • 実務で非常によく使うパターン

問題 15チャレンジ

総合売上分析

月次売上に、前月比、前年同月比、年初比を全て表示してください。

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS monthly_sales, — 前月比 ROUND( (SUM(total_amount) – LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) )) * 100.0 / LAG(SUM(total_amount), 1) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ), 2 ) AS mom_growth, — 前年同月比 ROUND( (SUM(total_amount) – LAG(SUM(total_amount), 12) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) )) * 100.0 / LAG(SUM(total_amount), 12) OVER ( ORDER BY strftime(‘%Y-%m’, order_date) ), 2 ) AS yoy_growth, — 年初比 ROUND( (SUM(total_amount) – FIRST_VALUE(SUM(total_amount)) OVER ( PARTITION BY strftime(‘%Y’, order_date) ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )) * 100.0 / FIRST_VALUE(SUM(total_amount)) OVER ( PARTITION BY strftime(‘%Y’, order_date) ORDER BY strftime(‘%Y-%m’, order_date) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ), 2 ) AS ytd_growth FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ORDER BY year_month;

解説:

  • 実務の売上分析で必須の3つの比較を全て表示
  • LAGとFIRST_VALUEを組み合わせた総合クエリ

📝 Step 9 のまとめ

✅ 学んだこと
  • LAG:前の行を参照(前月比、成長率など)
  • LEAD:次の行を参照(翌月予測、購入間隔など)
  • FIRST_VALUE:最初の値を取得(初回購入額、年初など)
  • LAST_VALUE:最後の値を取得(最終購入額、年末など)
  • 前月比・前年同月比:成長率の計算
  • 購入間隔:行動分析への活用
  • 4つの関数の組み合わせ:総合的な分析
📌 4つの関数まとめ

※横にスクロールできます

— LAG: N行前を取得 LAG(列名, N) OVER (ORDER BY 列名) — LEAD: N行後を取得 LEAD(列名, N) OVER (ORDER BY 列名) — FIRST_VALUE: 最初の値を取得 FIRST_VALUE(列名) OVER ( ORDER BY 列名 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) — LAST_VALUE: 最後の値を取得(UNBOUNDED FOLLOWINGが必須!) LAST_VALUE(列名) OVER ( ORDER BY 列名 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING )
⚠️ 注意点
  • LAST_VALUEUNBOUNDED FOLLOWING を必ず指定
  • LAG/LEAD:最初・最後の行はNULLになる(これは正常)
  • LAG(列, 12):12ヶ月前なので、最初の12ヶ月はNULL

❓ よくある質問

Q1: LAGとLEADの使い分けは?

LAGは過去との比較(前月比、成長率)、LEADは未来との比較(目標、予測)に使います。実務ではLAGの方がよく使います

Q2: LAG(列, 12)で前年同月比が計算できますか?

はい。月次データならLAG(列, 12)で12ヶ月前 = 前年同月のデータを取得できます。週次なら52、日次なら365です。

Q3: LAST_VALUEがうまく動きません

フレーム指定を確認してください。ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWINGがないと、「現在の行」が「最後」になってしまいます。

Q4: NULLが返ってくるのはなぜですか?

参照先がない場合にNULLが返ります。

  • LAG:最初の行(前がない)
  • LEAD:最後の行(次がない)

これは正常な動作です。COALESCEで0などに置き換えられます。

Q5: 前月比の計算式を簡単にできますか?

CTE(WITH句)を使うと読みやすくなります。同じLAGを何度も書かずに、1回の計算結果を再利用できます。

Q6: 実務で最もよく使うのは?

LAGでの前月比計算が圧倒的に多いです。売上分析、KPI追跡、レポート作成など、あらゆる場面で使われます。次に多いのはFIRST_VALUEでの年初比計算です。

🎓 次のステップでは

Step 10: CROSS JOINと自己結合では、特殊な結合方法を学びます。全組み合わせの生成や、同じテーブル同士の結合など、高度なデータ操作ができるようになります!

📝

学習メモ

SQL応用・パフォーマンス最適化 - Step 9

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE