NTILE() — データをN等分してランクグループに分類する
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;
以下の sales_scores テーブルから、各営業担当の「名前」「スコア」と、スコア降順で3等分したグループ番号(tier)を取得してください。
※ tier=1 が最上位(高スコア)グループ、tier=3 が最下位グループになります。
| sales_rep | score |
|---|---|
| Aさん | 95 |
| Bさん | 80 |
| Cさん | 65 |
| Dさん | 50 |
| Eさん | 35 |
| Fさん | 20 |
| sales_rep | score | tier |
|---|---|---|
| Aさん | 95 | 1 |
| Bさん | 80 | 1 |
| Cさん | 65 | 2 |
| Dさん | 50 | 2 |
| Eさん | 35 | 3 |
| Fさん | 20 | 3 |
ROWS BETWEEN — フレーム句で「移動平均」を計算する
ウィンドウ関数の 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日未満(序盤の行)の場合は、存在する行数のみで平均を計算すること。
| sale_date | revenue |
|---|---|
| 04-01 | 100 |
| 04-02 | 200 |
| 04-03 | 150 |
| 04-04 | 300 |
| 04-05 | 250 |
| sale_date | revenue | moving_avg_3d |
|---|---|---|
| 04-01 | 100 | 100.0 |
| 04-02 | 200 | 150.0 |
| 04-03 | 150 | 150.0 |
| 04-04 | 300 | 216.7 |
| 04-05 | 250 | 233.3 |
PERCENT_RANK() — グループ内のパーセンタイル順位を求める
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位まで表示。
| student_name | subject | score |
|---|---|---|
| Aさん | 数学 | 90 |
| Bさん | 数学 | 75 |
| Cさん | 数学 | 60 |
| Dさん | 英語 | 85 |
| Eさん | 英語 | 70 |
| student_name | subject | score | pct_rank |
|---|---|---|---|
| Cさん | 数学 | 60 | 0.00 |
| Bさん | 数学 | 75 | 0.50 |
| Aさん | 数学 | 90 | 1.00 |
| Eさん | 英語 | 70 | 0.00 |
| Dさん | 英語 | 85 | 1.00 |
LAG() + CTE — 前月比成長率(MoM Growth Rate)を計算する
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;
NULLIF などで分母を NULL に落とすのが定石です。直前の行が存在しない先頭行では LAG が NULL を返し、そこから計算した値も NULL になります。以下の monthly_revenue テーブルから、各月の「月(month)」「収益(revenue)」「前月収益(prev_revenue)」と、「前月比成長率 % (growth_rate_pct)」を取得してください。
※ 成長率は小数点第1位まで表示。最初の行(前月なし)は NULL で構いません。
| month | revenue |
|---|---|
| 2024-01 | 1000 |
| 2024-02 | 1200 |
| 2024-03 | 1100 |
| 2024-04 | 1500 |
| month | revenue | prev_revenue | growth_rate_pct |
|---|---|---|---|
| 2024-01 | 1000 | NULL | NULL |
| 2024-02 | 1200 | 1000 | 20.0 |
| 2024-03 | 1100 | 1200 | -8.3 |
| 2024-04 | 1500 | 1100 | 36.4 |
ROW_NUMBER() + サブクエリ — カテゴリ別 Top‑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は使わない)。選択行を確定させるため、商品名をタイブレーカーにすること。
| category | product | amount |
|---|---|---|
| ドリンク | コーヒー | 4000 |
| ドリンク | お茶 | 2800 |
| ドリンク | ジュース | 1500 |
| フード | りんご | 3000 |
| フード | バナナ | 2500 |
| フード | ぶどう | 1800 |
| category | product | amount |
|---|---|---|
| ドリンク | コーヒー | 4000 |
| ドリンク | お茶 | 2800 |
| フード | りんご | 3000 |
| フード | バナナ | 2500 |