順位を付ける関数(ROW_NUMBER / RANK / DENSE_RANK)の使い方
shain社員
| name | salary |
|---|---|
| 鈴木 | 980000 |
| 佐藤 | 720000 |
| 山田 | 700000 |
| 清水 | 650000 |
| 加藤 | 610000 |
| 中村 | 530000 |
| … | |
順位の列を足す順位を付けた結果
| name | salary | jun_i |
|---|---|---|
| 鈴木 | 980000 | 1 |
| 佐藤 | 720000 | 2 |
| 山田 | 700000 | 3 |
| 清水 | 650000 | 4 |
| 加藤 | 610000 | 5 |
| 中村 | 530000 | 6 |
| … | ||
rank() OVER (ORDER BY salary DESC) AS jun_i
- 青い列= 式から作った列。元の表には無い列です
- 社員 18 行 → 順位を付けた結果 18 行(行数は変わりません。減らすのは WHERE の仕事です)
元の行は 1 つも減りません。増えるのは順位の列だけです。給与の高い順に 1 から振られているのが見えます。
順位を付ける関数は、行を減らさずに「順位」の列を足します。 GROUP BY のように行が潰れないので、誰が何位かをそのまま出せます。row_number() rank() dense_rank() の 3 つがあり、違いが出るのは同額の行があるときだけです。
主要 7 製品で通用 そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database / IBM Db2
このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。
01順位は OVER の中の並びで決まる
結論: rank() OVER (ORDER BY 列 DESC) と書くと、その並びでの順位が列として付きます。
OVER が付く関数をウィンドウ関数と呼びます。OVER の中に書いた ORDER BY が「何の順で順位を付けるか」を決めます。
集約関数と違って行は潰れません。18 人いれば 18 行のまま、順位の列だけが増えます。
⚠️ OVER の中の ORDER BY と、いちばん外側の ORDER BY は別物です。 前者は順位の基準、後者は結果の並べ方です。片方だけ書いても SQL としては成立するので、書き忘れても気づきにくい場所です。
SELECT rank() OVER (ORDER BY salary DESC)★順位を付ける基準(給与の高い順)AS jun_i,作った列に名前を付けるname, salary元の列はそのまま出せるFROM shain行は減らないWHERE taishoku_on IS NULL在籍中だけを対象にするORDER BY jun_i, shain_id;★結果の並べ方(順位の基準とは別)
同じ ORDER BY でも、OVER の中にあるか外にあるかで役割がまったく違います。中は「順位の付け方」、外は「表示の並び」です。
SELECT rank() OVER (ORDER BY salary DESC) AS jun_i,
name,
salary
FROM shain
WHERE taishoku_on IS NULL
ORDER BY jun_i, shain_id;| jun_i | name | salary |
|---|---|---|
| 1 | 鈴木 | 980000 |
| 2 | 佐藤 | 720000 |
| 3 | 山田 | 700000 |
| 4 | 清水 | 650000 |
| 5 | 加藤 | 610000 |
| 6 | 中村 | 530000 |
| 6 | 伊藤 | 530000 |
| 8 | 田中 | 520000 |
| 9 | 井上 | 505000 |
| 10 | 吉田 | 468000 |
| 11 | 斎藤 | 455000 |
| 12 | 小林 | 445050 |
| 13 | 大野 | 432000 |
| 14 | 松本 | 421000 |
| 15 | 渡辺 | 400000 |
| 16 | 木村 | 398000 |
(16 行)
16 行返ります。18 人から退職者 2 人が WHERE で外れた数で、順位を付けたこと自体では 1 行も減っていません。
023 つの違いは同額の行でだけ出る
結論: 同額が無ければ 3 つとも同じ結果になります。 違いが出るのは同じ値の行が並んだときだけです。
このデータには給与が同額の社員が 2 人います(中村と伊藤の 530000)。そこで 3 つを横に並べると、違いがはっきり見えます。
rank()… 同額は同じ順位。次の順位は飛ばす(6, 6 の次は 8)dense_rank()… 同額は同じ順位。次の順位は飛ばさない(6, 6 の次は 7)row_number()… 同額でも必ず別の番号を振る(6, 7)
⚠️ 食い違いは同額の行だけでは終わりません。 dense_rank() は飛ばさない一方で rank() は飛ばすので、同額より下の行はずっと 1 つずれたままになります(田中は rank() で 8 位、dense_rank() で 7 位)。同額が 1 組あるだけで、以降の全行に差が出ます。
「◯位タイ」を表したいなら rank()、「上から何段階目か」を知りたいなら dense_rank()、とにかく重複しない通し番号が欲しいなら row_number() です。
SELECT name,
salary,
rank() OVER (ORDER BY salary DESC) AS rank_,
dense_rank() OVER (ORDER BY salary DESC) AS dense_,
row_number() OVER (ORDER BY salary DESC) AS row_
FROM shain
WHERE taishoku_on IS NULL
ORDER BY salary DESC, shain_id;| name | salary | rank_ | dense_ | row_ |
|---|---|---|---|---|
| 鈴木 | 980000 | 1 | 1 | 1 |
| 佐藤 | 720000 | 2 | 2 | 2 |
| 山田 | 700000 | 3 | 3 | 3 |
| 清水 | 650000 | 4 | 4 | 4 |
| 加藤 | 610000 | 5 | 5 | 5 |
| 中村 | 530000 | 6 | 6 | 7 |
| 伊藤 | 530000 | 6 | 6 | 6 |
| 田中 | 520000 | 8 | 7 | 8 |
| 井上 | 505000 | 9 | 8 | 9 |
| 吉田 | 468000 | 10 | 9 | 10 |
| 斎藤 | 455000 | 11 | 10 | 11 |
| 小林 | 445050 | 12 | 11 | 12 |
| 大野 | 432000 | 13 | 12 | 13 |
| 松本 | 421000 | 14 | 13 | 14 |
| 渡辺 | 400000 | 15 | 14 | 15 |
| 木村 | 398000 | 16 | 15 | 16 |
(16 行)
同額より上の 5 行は 3 つとも同じ値です。食い違いは中村・伊藤の行から始まります。rank は 7 を欠番にし、row_number は 6 と 7 に分け、dense_rank だけはそこから下もずれ続けます(田中は rank 8 / dense_rank 7)。
03⚠️ よくある間違い:同額のときの row_number は当てにしない
結論: 同額の行のうちどちらが先に番号をもらうかは、決まっていません。
上の実行結果では伊藤が 6、中村が 7 になっていますが、これは保証された順序ではありません。 OVER (ORDER BY salary DESC) は「給与の降順」としか言っておらず、給与が同じ 2 行のどちらを先にするかは何も指定していないからです。
あなたの手元では逆になることがあります。 製品が違えば、同じ製品でも実行計画が変われば、変わりえます。
通し番号を再現できる形にしたいなら、OVER の中の ORDER BY に決着が付く列を足してください。 ここでは社員番号を足せば、いつでも同じ結果になります。
SELECT name,
salary,
row_number() OVER (ORDER BY salary DESC, shain_id) AS row_
FROM shain
WHERE taishoku_on IS NULL
ORDER BY row_;| name | salary | row_ |
|---|---|---|
| 鈴木 | 980000 | 1 |
| 佐藤 | 720000 | 2 |
| 山田 | 700000 | 3 |
| 清水 | 650000 | 4 |
| 加藤 | 610000 | 5 |
| 中村 | 530000 | 6 |
| 伊藤 | 530000 | 7 |
| 田中 | 520000 | 8 |
| 井上 | 505000 | 9 |
| 吉田 | 468000 | 10 |
| 斎藤 | 455000 | 11 |
| 小林 | 445050 | 12 |
| 大野 | 432000 | 13 |
| 松本 | 421000 | 14 |
| 渡辺 | 400000 | 15 |
| 木村 | 398000 | 16 |
(16 行)
中村(社員番号 4)が 6、伊藤(社員番号 10)が 7 に固定されます。同額の 2 人の間に社員番号という決着が付いたためです。
04PARTITION BY でまとまりごとに振り直す
結論: OVER (PARTITION BY 列 ORDER BY …) と書くと、そのまとまりごとに順位が 1 から振り直されます。
「部署ごとの給与順位」のように、全体ではなくグループの中での順位が欲しいときに使います。GROUP BY と違って行は潰れないので、誰が何位かがそのまま出ます。
⚠️ 所属が未設定の社員(busho_id が NULL)も、NULL 同士でひとつのまとまりとして扱われます。等しさの判定とは違う扱いなので、WHERE の = NULL の話と混同しないでください。
SELECT busho_id,
name,
salary,
rank() OVER (PARTITION BY busho_id ORDER BY salary DESC) AS busho_jun_i
FROM shain
ORDER BY busho_id, busho_jun_i, shain_id;| busho_id | name | salary | busho_jun_i |
|---|---|---|---|
| 1 | 鈴木 | 980000 | 1 |
| 2 | 佐藤 | 720000 | 1 |
| 2 | 中村 | 530000 | 2 |
| 2 | 田中 | 520000 | 3 |
| 2 | 小林 | 445050 | 4 |
| 3 | 山田 | 700000 | 1 |
| 3 | 伊藤 | 530000 | 2 |
| 3 | 高橋 | 480000 | 3 |
| 3 | 松本 | 421000 | 4 |
| 4 | 清水 | 650000 | 1 |
| 4 | 山本 | 512000 | 2 |
| 4 | 斎藤 | 455000 | 3 |
| 5 | 加藤 | 610000 | 1 |
| 5 | 吉田 | 468000 | 2 |
| 7 | 井上 | 505000 | 1 |
| 7 | 木村 | 398000 | 2 |
| NULL | 大野 | 432000 | 1 |
| NULL | 渡辺 | 400000 | 2 |
(18 行)
部署が変わるたびに順位が 1 に戻ります。末尾の 2 行は所属が未設定の社員で、NULL 同士がひとつのまとまりとして 1 位・2 位になっています。
05⚠️ よくある間違い:WHERE では使えない
結論: WHERE にウィンドウ関数は書けません。エラーになります。
WHERE はウィンドウ関数より先に評価されるためです。「どの行を残すか」が決まる時点では、まだ順位は付いていません。
そのため「上位 3 位だけ表示」は 1 つの SELECT では書けません。いったん順位を付けた結果を表として作ってから、その外側で絞り込みます。
書き方は 2 つあり、どちらでも同じことができます。
FROMの中に副問い合わせを置く(派生表)WITHで名前を付けてから使う
⚠️ HAVING も同じ理由で使えません。順位で絞りたくなったら、必ず一段外に出すと覚えてください。
SELECT name, salary
FROM shain
WHERE rank() OVER (ORDER BY salary DESC) <= 3;「window functions are not allowed in WHERE」と出ます。書き方の間違いではなく、評価される順番の問題です。
SELECT name, salary, jun_i
FROM (SELECT name,
salary,
rank() OVER (ORDER BY salary DESC) AS jun_i
FROM shain
WHERE taishoku_on IS NULL) t
WHERE jun_i <= 3
ORDER BY jun_i;| name | salary | jun_i |
|---|---|---|
| 鈴木 | 980000 | 1 |
| 佐藤 | 720000 | 2 |
| 山田 | 700000 | 3 |
(3 行)
3 行返ります。内側で順位を付け終えた表を作り、その外側の WHERE で 3 位までに絞っています。
自分で打ってみる
このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。
押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。
解いてみる
読んだだけでは書けるようになりません。実行結果で採点します。
ここで出てくる関数を引く
- FIRST_VALUE / LAST_VALUE区切りの中の最初・最後の値を持ってくる。LAST_VALUE は既定のままだと期待どおりに動かない。
- LAG / LEAD並べた中で、1 つ前(LAG)や 1 つ後ろ(LEAD)の行の値を持ってくる。前月比などに使う。
- NTH_VALUE区切りの中の n 番目の値を返す。既定のフレームだと n 番目がまだ見えないことがある。
- NTILE並べた行を、指定した数のグループにできるだけ均等に振り分ける。
- PERCENT_RANK / CUME_DIST順位を 0〜1 の割合で返す。2 つは端の扱いが違う。
- RANK / DENSE_RANK順位を付ける。同じ値には同じ順位が付く。RANK は次の番号が飛び、DENSE_RANK は飛ばない。
- ROW_NUMBER並べた順に 1, 2, 3 … と通し番号を振る。同じ値でも必ず別の番号になる。
関連するトピック
- 集約関数のウィンドウ化同じ OVER を集約関数に付けると、合計や平均を行ごとに配れます。
- 集約関数行が潰れるほうの集約はこちらです。
- GROUP BY 句まとまりごとに 1 行へまとめる書き方です。
- FROM 句の副問い合わせ順位で絞り込むときに使う「表を作ってから使う」書き方です。
- ORDER BY 句結果の並べ方そのものはこちらです。
根拠(一次情報)
- PostgreSQL 18 マニュアル: ウィンドウ関数(row_number / rank / dense_rank)一次情報・確認 2026-08-11
- PostgreSQL 18 マニュアル: ウィンドウ関数の評価順序(WHERE より後)一次情報・確認 2026-08-11