SQL ウィンドウ関数 — フレーム句・Top-N抽出の応用

応用ウィンドウ関数NTILE / PERCENT_RANKフレーム句前月比 / 成長率グループTop-NPostgreSQL/MySQL8.0+/BigQuery対応全5問
1 / 5 · 学習中 0 / 5 問完了
QUESTION 1

NTILE() — データをN等分してランクグループに分類する

NTILEORDER BY分位数グループ化均等分割
前提知識

NTILE(N) は全行を N 個の均等グループに分割し、各行に 1〜N のグループ番号(ランク)を付与する関数です。「上位25%の顧客」「スコア下位10%の商品」のような分位数グループ化に使います。

SELECT
  name, score,
  NTILE(4) OVER(ORDER BY score DESC) AS quartile
  -- スコア降順に並べ全行を4等分。上位25%がquartile=1
FROM students;
均等に割り切れない場合:余りの行は番号の小さいグループから1行ずつ多く配分されます。例: NTILE(4) で6行なら、グループ1・2が2行、グループ3・4が1行になります。
問題

以下の sales_scores テーブルから、各営業担当の「名前」「スコア」と、スコア降順で3等分したグループ番号(tier)を取得してください。

※ tier=1 が最上位(高スコア)グループ、tier=3 が最下位グループになります。

使用テーブル
▸ sales_scores
sales_repscore
Aさん95
Bさん80
Cさん65
Dさん50
Eさん35
Fさん20
期待出力
sales_repscoretier
Aさん951
Bさん801
Cさん652
Dさん502
Eさん353
Fさん203
QUESTION 2

ROWS BETWEEN — フレーム句で「移動平均」を計算する

ROWS BETWEENORDER BY移動平均フレーム句
前提知識

ウィンドウ関数の OVER() 内に指定できるフレーム句は、「各行に対してどの範囲の行を計算に使うか」を定義します。

AVG(revenue) OVER(
  ORDER BY sale_date
  ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
  -- 現在行とその直前2行(合計最大3行)が計算対象
)
代表的なフレーム指定:
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: 先頭行〜現在行(累積集計)
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW: 直前2行+現在行(3行移動平均)
・フレーム句なし + ORDER BY あり → デフォルトは RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(累積)
問題

以下の daily_sales テーブルから、各日付の「売上(revenue)」と、「直近3日間の移動平均(moving_avg_3d)」を取得してください。

※ 移動平均は「現在行 + 直前2行」の最大3行の平均とし、小数点第1位まで表示すること。
※ データが3日未満(序盤の行)の場合は、存在する行数のみで平均を計算すること。

使用テーブル
▸ daily_sales
sale_daterevenue
04-01100
04-02200
04-03150
04-04300
04-05250
期待出力
sale_daterevenuemoving_avg_3d
04-01100100.0
04-02200150.0
04-03150150.0
04-04300216.7
04-05250233.3
QUESTION 3

PERCENT_RANK() — グループ内のパーセンタイル順位を求める

PERCENT_RANKPARTITION BY百分位順位相対評価
前提知識

PERCENT_RANK() は、グループ内で「自分の値が全体の何パーセントの位置にいるか」を 0.0〜1.0 の値で返す関数です。よく似た CUME_DIST() は「現在値以下の行数 ÷ 総行数」を返すため、最小値が0にならない点が異なります。

PERCENT_RANK() OVER(
  PARTITION BY subject
  ORDER BY score ASC
) AS pct_rank
値の求め方:複数行のグループでは (順位 - 1)÷(行数 - 1) で計算します。昇順なら最低スコアが0.00になり、一意の最高スコアは1.00になります。同率の最高スコアは順位を共有するため、1.00未満になることがあります。1行だけのグループは0.00です。
問題

以下の test_scores テーブルから、各生徒の「名前」「科目」「点数」と、「科目内でのパーセンタイル順位(pct_rank)」を取得してください。

※ 科目ごとに独立して計算し、点数の低い順(ASC)で 0.0〜1.0 の値を付与すること。小数点第2位まで表示。

使用テーブル
▸ test_scores
student_namesubjectscore
Aさん数学90
Bさん数学75
Cさん数学60
Dさん英語85
Eさん英語70
期待出力
student_namesubjectscorepct_rank
Cさん数学600.00
Bさん数学750.50
Aさん数学901.00
Eさん英語700.00
Dさん英語851.00
QUESTION 4

LAG() + CTE — 前月比成長率(MoM Growth Rate)を計算する

LAGCTE成長率計算前月比分析
前提知識

LAG() で取得した直前行の値と現在の値を組み合わせることで、前月比成長率のような「変化率」が計算できます。

成長率の計算式: (当月 − 前月) ÷ 前月 × 100

ただし、LAG() を SELECT 句で何度も繰り返して書くと、ウィンドウ計算が重複しパフォーマンスが劣化することがあります。CTE(WITH 句)でLAGを一度だけ計算し、外側クエリで成長率を算出するのがベストプラクティスです。

WITH base AS (
  SELECT sort_col, num_col,
    LAG(num_col) OVER(ORDER BY sort_col) AS prev_val
    -- CTEで一度だけLAGを評価し、外側で再利用する
  FROM table_name
)
SELECT *, (num_col - prev_val) * 100.0 / NULLIF(prev_val, 0) AS rate_pct
FROM base;
ゼロ除算と NULL:分母になる直前行の値が 0 だと除算でエラーになるため、NULLIF などで分母を NULL に落とすのが定石です。直前の行が存在しない先頭行では LAGNULL を返し、そこから計算した値も NULL になります。
問題

以下の monthly_revenue テーブルから、各月の「月(month)」「収益(revenue)」「前月収益(prev_revenue)」と、「前月比成長率 % (growth_rate_pct)」を取得してください。

※ 成長率は小数点第1位まで表示。最初の行(前月なし)は NULL で構いません。

使用テーブル
▸ monthly_revenue
monthrevenue
2024-011000
2024-021200
2024-031100
2024-041500
期待出力
monthrevenueprev_revenuegrowth_rate_pct
2024-011000NULLNULL
2024-021200100020.0
2024-0311001200-8.3
2024-041500110036.4
QUESTION 5

ROW_NUMBER() + サブクエリ — カテゴリ別 Top‑N 件を抽出する

ROW_NUMBERPARTITION BYTop-N抽出実務パターン
前提知識

ウィンドウ関数の結果(行番号)は、サブクエリや CTE を介さないと WHERE 句でフィルタリングできません。これは実行順序の問題です(WHERE はウィンドウ関数の評価より先に処理される)。

-- ✗ これはエラー: ウィンドウ関数の結果を直接 WHERE では使えない
SELECT * FROM t
WHERE ROW_NUMBER() OVER(...) <= 2;  -- SQL Error!

-- ✓ サブクエリでラップして WHERE でフィルタリング
SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER(...) AS rn FROM t
) ranked
WHERE rn <= 2;
タイブレーカーが要る理由:ROW_NUMBER() は同点の行にも必ず異なる番号を振りますが、どちらを先にするかは並び順が同値のままでは決まりません。同値になりうるときは一意な列を ORDER BY の末尾に足し、どの行が選ばれるかを確定させます。
問題

以下の product_sales テーブルから、カテゴリ別に売上金額の上位2件のみを取得してください。

※ 同じカテゴリ内で金額が同じ場合は、行番号を一意に割り当てる ROW_NUMBER() を使用すること(DENSE_RANKは使わない)。選択行を確定させるため、商品名をタイブレーカーにすること。

使用テーブル
▸ product_sales
categoryproductamount
ドリンクコーヒー4000
ドリンクお茶2800
ドリンクジュース1500
フードりんご3000
フードバナナ2500
フードぶどう1800
期待出力
categoryproductamount
ドリンクコーヒー4000
ドリンクお茶2800
フードりんご3000
フードバナナ2500