LEAD() — 次の行の値を先読みして成長率・差分を計算する
LEAD(expr, offset, default) は、現在の行から offset 行だけ先(未来)の値を返す関数です。デフォルトは1行先。パーティションの最終行など「次の行が存在しない」場合は default の値(省略時は NULL)を返します。
LEAD(revenue) -- 1行先の revenue(デフォルト) LEAD(revenue, 2) -- 2行先の revenue LEAD(revenue, 1, 0) -- 1行先、存在しない場合は 0 を返す
LAG() が「過去(前の行)」を参照するのに対し、LEAD() は「未来(次の行)」を参照します。どちらも「次/前」がどの行かは ORDER BY が決め、月次・週次の前期比や成長率の計算に頻出します。以下の monthly_revenue テーブルから、各月の売上と「翌月の売上(next_revenue)」、および「翌月との差分(diff = next_revenue − revenue)」を出力してください。翌月が存在しない場合は NULL としてください。
| month | revenue |
|---|---|
| 2026-01 | 100 |
| 2026-02 | 120 |
| 2026-03 | 90 |
| 2026-04 | 150 |
| month | revenue | next_revenue | diff |
|---|---|---|---|
| 2026-01 | 100 | 120 | +20 |
| 2026-02 | 120 | 90 | -30 |
| 2026-03 | 90 | 150 | +60 |
| 2026-04 | 150 | NULL | NULL |
FIRST_VALUE() — カテゴリ内トップとの差分を一発で計算する
FIRST_VALUE(expr) は、ウィンドウフレームの最初の行の値を返す関数です。PARTITION BY category ORDER BY score DESC と組み合わせると、「そのカテゴリでスコアが最も高い値(トップ)」を常に取得できます。
FIRST_VALUE(score) OVER( PARTITION BY category ORDER BY score DESC ) AS top_score -- カテゴリ内 最高スコア
ORDER BY を指定した場合のデフォルトフレーム(通常は RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)には先頭行が常に含まれるため、どの行を処理中でも先頭行の値が返されます。同点行があると同じ並び順の値を持つ行がピアとして同じフレームに入りますが、FIRST_VALUE が返す値自体は変わりません。以下の product_scores テーブルから、各商品のスコアと、「同じカテゴリ内で最高のスコア(top_score)」および「トップとの差(gap = top_score − score)」を出力してください。カテゴリ内でスコアが高い順に並べてください。
| product | category | score |
|---|---|---|
| P1 | A | 85 |
| P2 | A | 92 |
| P3 | A | 78 |
| P4 | B | 70 |
| P5 | B | 88 |
| product | category | score | top_score | gap |
|---|---|---|---|---|
| P2 | A | 92 | 92 | 0 |
| P1 | A | 85 | 92 | 7 |
| P3 | A | 78 | 92 | 14 |
| P5 | B | 88 | 88 | 0 |
| P4 | B | 70 | 88 | 18 |
移動平均 — 直近3日間のスライディング・ウィンドウを実装する
移動平均(Moving Average)は、直近N期分のデータの平均を計算する手法で、株価・売上・アクセス数などのトレンド把握に頻用されます。ウィンドウ関数では ROWS BETWEEN でフレームを指定することで実現します。
AVG(amount) OVER( ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) -- 直近3日(2行前〜現在行)の平均
以下の daily_sales テーブルから、各日の売上と「直近3日間(当日を含む)の売上移動平均(moving_avg_3)」を計算してください。この表は日付の欠損がない1日1行のデータです。日付昇順で出力し、3日未満のデータがある初期の行については、存在する行のみで平均を計算してください。
| sale_date | amount |
|---|---|
| 04-01 | 30 |
| 04-02 | 50 |
| 04-03 | 40 |
| 04-04 | 60 |
| 04-05 | 80 |
| sale_date | amount | moving_avg_3 |
|---|---|---|
| 04-01 | 30 | 30.00 |
| 04-02 | 50 | 40.00 |
| 04-03 | 40 | 40.00 |
| 04-04 | 60 | 50.00 |
| 04-05 | 80 | 60.00 |
WINDOW句 — 共通ウィンドウ定義でDRY(重複排除)なSQLを書く
同じ PARTITION BY ... ORDER BY ... を複数のウィンドウ関数で繰り返す場合、WINDOW句を使うと名前付きウィンドウを定義して使い回せます(DRY: Don't Repeat Yourself 原則)。
SELECT ROW_NUMBER() OVER(w) AS rank, -- 同じウィンドウ w を参照 SUM(sales) OVER(w) AS running, LAG(sales) OVER(w) AS prev FROM t WINDOW w AS (PARTITION BY emp_id ORDER BY month); -- 1箇所で定義
FROM・WHERE・GROUP BY の後、ORDER BY の前に書きます。以下の employee_sales テーブルから、各従業員・月ごとに以下の3指標を付与してください。WINDOW句を使って PARTITION BY emp_id, dept ORDER BY month の定義を1箇所にまとめてください。
- rank:その社員のその月が「何ヶ月目か」(ROW_NUMBER)
- running:その社員のその時点までの累計売上(SUM)
- prev_sales:その社員の前月の売上(LAG)。初月は NULL
| emp_id | dept | month | sales |
|---|---|---|---|
| E1 | Sales | 01 | 100 |
| E1 | Sales | 02 | 120 |
| E2 | Sales | 01 | 80 |
| E2 | Sales | 02 | 90 |
| E3 | Tech | 01 | 150 |
| emp_id | dept | month | sales | rank | running | prev_sales |
|---|---|---|---|---|---|---|
| E1 | Sales | 01 | 100 | 1 | 100 | NULL |
| E1 | Sales | 02 | 120 | 2 | 220 | 100 |
| E2 | Sales | 01 | 80 | 1 | 80 | NULL |
| E2 | Sales | 02 | 90 | 2 | 170 | 80 |
| E3 | Tech | 01 | 150 | 1 | 150 | NULL |
応用総まとめ — CTE + LAG + SUM でセッション分析を実装する
セッション分析は、ユーザーの行動ログから「1回のセッション(連続したアクション)」を識別するプロダクト分析の基本技術です。「前のイベントから30分を超えて経過したら新しいセッション開始」というルールを LAG + CASE WHEN で検出し、SUM の累積でセッション番号を振ります。差がちょうど30分なら同じセッションです。
WITH flagged AS ( SELECT ..., CASE WHEN LAG(ts_col) OVER(...) IS NULL OR (ts_col - LAG(ts_col) OVER(...)) > gap_limit THEN 1 ELSE 0 END AS is_new_session FROM table_name ) SELECT ..., SUM(is_new_session) OVER(PARTITION BY key_col ORDER BY ts_col) AS session_id FROM flagged;
event_time は「深夜0時からの経過分数」を表す整数です(例:600 = 10:00、660 = 11:00)。したがって時間差は単純な引き算で求まります。実際のDBでは DATEDIFF や EXTRACT(EPOCH FROM ...) 等で時間差を計算します。以下の user_events テーブルから、ユーザーごとに「前のイベントから30分(event_time の差が30)を超えたら新しいセッション」というルールでセッション番号(session_id)を付与してください。各ユーザーの最初のイベントはセッション1から始めます。
| user_id | event_time | event_type |
|---|---|---|
| U1 | 600 | login |
| U1 | 605 | click |
| U1 | 645 | click |
| U1 | 650 | logout |
| U2 | 660 | login |
| U2 | 670 | click |
| user_id | event_time | event_type | session_id |
|---|---|---|---|
| U1 | 600 | login | 1 |
| U1 | 605 | click | 1 |
| U1 | 645 | click | 2 |
| U1 | 650 | logout | 2 |
| U2 | 660 | login | 1 |
| U2 | 670 | click | 1 |