【完全比較】Oracle RANK / DENSE_RANK / ROW_NUMBER の違いと使い分け完全ガイド
- 作成日 2026.09.11
- Oracle Database
Oracle SQL 開発者がランキング処理で必ず使う 3 大分析関数、それが RANK / DENSE_RANK / ROW_NUMBER です:
SELECT ename, deptno, sal,
RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS dense_rnk,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) AS row_num
FROM emp;
Oracle 8i (1999) から提供される最頻用の分析関数群。しかし、3 つの違いを厳密に理解している開発者は少ない:
RANK(): 同順位に同じランク、次はスキップ(1, 2, 2, 4, 5)DENSE_RANK(): 同順位に同じランク、次も連続(1, 2, 2, 3, 4)ROW_NUMBER(): 必ず一意な連番、TIE は任意順序(1, 2, 3, 4, 5)
このセットの本質は、Oracle 公式 Database SQL Language Reference が定義:
「RANK 系は、ORDER BY 条件でソートされた行に順位を付与する分析関数。
同順位の扱いが 3 関数で異なり、
用途に応じて厳密に使い分ける必要がある」
3 関数の違いを 1 表で
サラリー 5000, 3000, 3000, 2500 の 4 人の場合:
Sal RANK DENSE_RANK ROW_NUMBER
---- ---- ---------- ----------
5000 1 1 1
3000 2 2 2 ← TIE
3000 2 2 3 ← TIE
2500 4 3 4
↑ ↑ ↑
3 なし 連続的 必ず一意
具体的な使い分け
RANK: 「同順位はまとめて、次は飛ばす」オリンピック方式
-- 給与が高い上位 3 位(TIE 含む可能性あり)
SELECT ename, sal FROM (
SELECT ename, sal, RANK() OVER (ORDER BY sal DESC) AS r FROM emp
) WHERE r <= 3;
-- 2 位が 2 人なら 4 人返る(3 位なし)
DENSE_RANK: 「同順位はまとめて、次は連続」順位表方式
-- 上位 3 位のサラリーの人(TIE 含む)
SELECT ename, sal FROM (
SELECT ename, sal, DENSE_RANK() OVER (ORDER BY sal DESC) AS r FROM emp
) WHERE r <= 3;
-- 同順位含めて広く抽出
ROW_NUMBER: 「必ず一意な連番」ページング方式
-- ページング(10 行ごと)
SELECT * FROM (
SELECT e.*, ROW_NUMBER() OVER (ORDER BY sal DESC) AS rn FROM emp e
) WHERE rn BETWEEN 11 AND 20;
典型的な用途
現場で最頻出の使い分け:
【ROW_NUMBER】
- ページング(OFFSET-FETCH 代替)
- 重複排除(DISTINCT より柔軟)
- Top N per group(大多数のケース)
- ユニーク行番号必要な処理
【RANK】
- 「上位 N 位」(オリンピック方式)
- スコアランキング(同点は同順位、次は飛ぶ)
- 順位が「離散的」でよい
【DENSE_RANK】
- Top N distinct values
- 順位表(同点は同順位、次は詰める)
- パーセンタイル計算
- 分位分割の前処理
現場で使う分析関数の主要機能:
OVER (PARTITION BY ... ORDER BY ...)の完全構文NULLS FIRST/NULLS LASTによる NULL 処理Top N per group(部門別給与 Top 3 等、超典型)ページング(ROW_NUMBER)重複排除(ROW_NUMBER + PARTITION BY)集計関数版(RANK(...) WITHIN GROUP)- 姉妹関数:
NTILE,PERCENT_RANK,CUME_DIST FIRST_VALUE/LAST_VALUEによる参照LAG/LEADによる前後行参照- パフォーマンス(適切なインデックス)
WITH TIES(12c+ FETCH 文の類似機能)
多くの日本語記事が「3 つは似ている」で終わりますが、実務では:
- TIE の扱いによる結果件数の違い
PARTITION BYによるグループ内順位NULLS FIRST/LASTの明示的指定の重要性ROW_NUMBER+PARTITION BYによる重複排除の定番パターンROW_NUMBER+ サブクエリによるページングDENSE_RANKによるMedian 計算RANKの集計関数版による特定値の順位取得NTILEによる分位分割PERCENT_RANK/CUME_DISTによるパーセンタイル- Rails / Java / Python アプリでの分析関数活用
- OFFSET-FETCH (12c+) との性能比較
さらに、Oracle の 3 関数の本質的違いを数式で表現:
RANK(r) = 「r より小さい行の数」 + 1
DENSE_RANK(r) = 「r より小さい ORDER BY 値の distinct 数」 + 1
ROW_NUMBER(r) = 「ORDER BY 順の一意な連番」(TIE は不定)
本記事では、Oracle RANK / DENSE_RANK / ROW_NUMBER の全てを実務レベルで完全比較・網羅。基本構文、TIE 処理の違い、OVER 句、10 大実用例、姉妹関数、パフォーマンス、Rails/Java/Python 統合、実践シナリオ、FAQまで。この1本で 3 つの分析関数をプロフェッショナルに使い分けられるようになります。
結論:TIE 処理と用途で選ぶ
時間がない方向けに、最速の使い分けを先に示します。
用途別チャート
一意な行番号が必要?
├── YES → ROW_NUMBER()
│ └── ページング、重複排除、Top N(TIE 気にしない)
└── NO
├── 同順位の後を「スキップ」する?
│ └── YES → RANK() ← オリンピック方式
└── 同順位の後も「連続」する?
└── YES → DENSE_RANK() ← 順位表方式
3 関数の結果比較(1 行で理解)
Sal RANK DENSE_RANK ROW_NUMBER
---- ---- ---------- ----------
5000 1 1 1
3000 2 2 2
3000 2 2 3
2500 4 3 4
2500 4 3 5
1000 6 4 6
基本構文
FUNCTION() OVER (
[PARTITION BY partition_col]
ORDER BY sort_col [ASC|DESC] [NULLS FIRST|LAST]
)
6つの必須知識
| # | トピック | 概要 |
|---|---|---|
| ① | RANK | TIE で GAP あり |
| ② | DENSE_RANK | TIE で GAP なし |
| ③ | ROW_NUMBER | 一意な連番 |
| ④ | PARTITION BY | グループ内順位 |
| ⑤ | NULLS FIRST/LAST | NULL 位置制御 |
| ⑥ | Top N per group | 定番パターン |
詳細は以下で解説します。
3 関数の動作詳解
サンプルテーブル(emp)
CREATE TABLE emp (
empno NUMBER PRIMARY KEY,
ename VARCHAR2(50),
deptno NUMBER,
sal NUMBER
);
INSERT INTO emp VALUES (7839, 'KING', 10, 5000);
INSERT INTO emp VALUES (7782, 'CLARK', 10, 2450);
INSERT INTO emp VALUES (7934, 'MILLER', 10, 1300);
INSERT INTO emp VALUES (7788, 'SCOTT', 20, 3000);
INSERT INTO emp VALUES (7902, 'FORD', 20, 3000); -- TIE
INSERT INTO emp VALUES (7566, 'JONES', 20, 2975);
INSERT INTO emp VALUES (7876, 'ADAMS', 20, 1100);
INSERT INTO emp VALUES (7369, 'SMITH', 20, 800);
COMMIT;
3 関数同時実行
SELECT deptno, ename, sal,
RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS d_rnk,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) AS rn
FROM emp
ORDER BY deptno, sal DESC;
-- 結果:
-- DEPTNO ENAME SAL RNK D_RNK RN
-- ------ ------- ---- ---- ----- ---
-- 10 KING 5000 1 1 1
-- 10 CLARK 2450 2 2 2
-- 10 MILLER 1300 3 3 3
-- 20 SCOTT 3000 1 1 1 ← TIE 開始
-- 20 FORD 3000 1 1 2 ← TIE、ROW_NUMBER は連続
-- 20 JONES 2975 3 2 3 ← RANK は 2 を飛ばして 3
-- 20 ADAMS 1100 4 3 4 DENSE_RANK は 2
-- 20 SMITH 800 5 4 5
3 関数の本質的違い
RANK():
「自分より上位の行数 + 1」
5000: 上位 0 行 → 1
3000, 3000: 上位 1 行 → 2
2975: 上位 3 行 → 4 ← 「上位」を「同順位含む」でカウント
DENSE_RANK():
「自分より上位の distinct 値の数 + 1」
5000: 上位 distinct 0 → 1
3000: 上位 distinct 1 → 2
2975: 上位 distinct 2 → 3 ← distinct でカウント
ROW_NUMBER():
「必ず一意な連番」
5000: 1
3000: 2
3000: 3 ← TIE でも一意
2975: 4
OVER 句の完全解説
PARTITION BY
グループ内順位:
-- 部門別の給与順位
RANK() OVER (PARTITION BY deptno ORDER BY sal DESC)
-- 各部門で独立して 1 位からカウント
なし = 全体順位:
-- 全社の給与順位
RANK() OVER (ORDER BY sal DESC)
ORDER BY
ソート順で順位付け:
-- 降順(高い方が 1 位)
RANK() OVER (ORDER BY sal DESC)
-- 昇順(低い方が 1 位)
RANK() OVER (ORDER BY sal ASC)
複数列:
-- 部門優先、次に給与
RANK() OVER (ORDER BY deptno, sal DESC)
NULLS FIRST / NULLS LAST
NULL の位置制御(明示推奨):
-- NULL を最初に
ORDER BY sal DESC NULLS FIRST
-- NULL を最後に
ORDER BY sal DESC NULLS LAST
-- ⚠️ デフォルト:
-- DESC → NULLS FIRST
-- ASC → NULLS LAST
PARTITION BY + ORDER BY
最も一般的:
SELECT deptno, ename, sal,
ROW_NUMBER() OVER (
PARTITION BY deptno -- 部門ごとに
ORDER BY sal DESC -- 給与順
NULLS LAST -- NULL は最後
) AS rn
FROM emp;
実用例10選
例1: 部門別給与 Top 3
RANK 版(TIE 含む Top 3 位):
SELECT deptno, ename, sal, r
FROM (
SELECT deptno, ename, sal,
RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS r
FROM emp
)
WHERE r <= 3;
DENSE_RANK 版(Top 3 位の値):
SELECT deptno, ename, sal, dr
FROM (
SELECT deptno, ename, sal,
DENSE_RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS dr
FROM emp
)
WHERE dr <= 3;
ROW_NUMBER 版(正確に 3 行):
SELECT deptno, ename, sal, rn
FROM (
SELECT deptno, ename, sal,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) AS rn
FROM emp
)
WHERE rn <= 3;
例2: ページング(OFFSET-FETCH 代替)
-- 11-20 行目を取得
SELECT * FROM (
SELECT e.*,
ROW_NUMBER() OVER (ORDER BY sal DESC) AS rn
FROM emp e
)
WHERE rn BETWEEN 11 AND 20;
-- 12c+ の OFFSET-FETCH との比較
SELECT * FROM emp
ORDER BY sal DESC
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
例3: 重複排除(超典型パターン)
-- email 重複を最新 created_at で 1 行だけ残す
DELETE FROM users
WHERE ROWID IN (
SELECT ROWID FROM (
SELECT ROWID,
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY created_at DESC
) AS rn
FROM users
)
WHERE rn > 1
);
例4: 各部門の最高給与者
SELECT deptno, ename, sal
FROM (
SELECT deptno, ename, sal,
ROW_NUMBER() OVER (
PARTITION BY deptno ORDER BY sal DESC
) AS rn
FROM emp
)
WHERE rn = 1;
例5: Median(中央値)計算
SELECT AVG(sal) AS median_sal
FROM (
SELECT sal,
DENSE_RANK() OVER (ORDER BY sal) AS r_asc,
DENSE_RANK() OVER (ORDER BY sal DESC) AS r_desc
FROM emp
)
WHERE r_asc = r_desc OR r_asc + 1 = r_desc OR r_asc = r_desc + 1;
-- または Oracle の MEDIAN 関数
SELECT MEDIAN(sal) FROM emp;
例6: N 番目の値
-- 部門別 2 番目に高い給与
SELECT deptno, sal
FROM (
SELECT deptno, sal,
DENSE_RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS r
FROM emp
)
WHERE r = 2;
例7: パーセンタイル分位分割
-- 給与を 4 分位に分割
SELECT deptno, ename, sal,
NTILE(4) OVER (ORDER BY sal DESC) AS quartile
FROM emp;
-- 上位 25% のみ
SELECT deptno, ename, sal
FROM (
SELECT deptno, ename, sal,
NTILE(4) OVER (ORDER BY sal DESC) AS quartile
FROM emp
)
WHERE quartile = 1;
例8: 累積・変化(LAG/LEAD)
SELECT deptno, ename, sal,
LAG(sal) OVER (PARTITION BY deptno ORDER BY sal) AS prev_sal,
sal - LAG(sal) OVER (PARTITION BY deptno ORDER BY sal) AS diff,
LEAD(sal) OVER (PARTITION BY deptno ORDER BY sal) AS next_sal
FROM emp;
例9: 最初/最後の値(FIRST_VALUE/LAST_VALUE)
SELECT deptno, ename, sal,
FIRST_VALUE(ename) OVER (
PARTITION BY deptno ORDER BY sal DESC
) AS top_earner,
LAST_VALUE(ename) OVER (
PARTITION BY deptno ORDER BY sal DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS bottom_earner
FROM emp;
例10: 集計関数版 RANK
-- 特定給与の順位取得
SELECT RANK(3000) WITHIN GROUP (ORDER BY sal DESC) AS rank_of_3000
FROM emp;
-- 「3000 だったらこの順位」
SELECT DENSE_RANK(3000) WITHIN GROUP (ORDER BY sal DESC) AS d_rank_of_3000
FROM emp;
姉妹分析関数
NTILE(分位分割)
-- 4 分割
NTILE(4) OVER (ORDER BY sal DESC)
-- 1: 上位 25%
-- 2: 25-50%
-- 3: 50-75%
-- 4: 下位 25%
PERCENT_RANK(0-1 の比率)
PERCENT_RANK() OVER (ORDER BY sal DESC)
-- 最高: 0
-- 最低: 1
-- 中間: 比率
CUME_DIST(累積分布)
CUME_DIST() OVER (ORDER BY sal DESC)
-- 累積確率
FIRST_VALUE / LAST_VALUE
FIRST_VALUE(sal) OVER (PARTITION BY deptno ORDER BY hire_date)
-- パーティション内の最初の値
LAG / LEAD
LAG(sal, 1, 0) OVER (ORDER BY hire_date) -- 1 行前、なければ 0
LEAD(sal, 1, 0) OVER (ORDER BY hire_date) -- 1 行後
ROW_NUMBER と一緒に使うパターン
-- 前後行との差分
SELECT sal,
sal - LAG(sal, 1, 0) OVER (ORDER BY sal) AS diff_from_prev,
ROW_NUMBER() OVER (ORDER BY sal) AS rn
FROM emp;
パフォーマンス最適化
インデックス
-- ORDER BY と PARTITION BY 列にインデックス
CREATE INDEX idx_emp_dept_sal ON emp(deptno, sal DESC);
-- 分析関数は Sort Operation を回避可
実行計画確認
EXPLAIN PLAN FOR
SELECT ename, sal,
ROW_NUMBER() OVER (ORDER BY sal DESC) AS rn
FROM emp;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- WINDOW SORT が現れる
-- インデックス活用で SORT 回避可
実行計画関連は Oracle EXPLAIN PLAN 完全ガイドの記事も参照してください。
Top N の効率
ROW_NUMBER でサブクエリ:
SELECT * FROM (
SELECT e.*, ROW_NUMBER() OVER (ORDER BY sal DESC) AS rn FROM emp e
) WHERE rn <= 10;
12c+ FETCH FIRST:
SELECT * FROM emp ORDER BY sal DESC FETCH FIRST 10 ROWS ONLY;
FETCH FIRST の方が最適化される傾向。
WITH TIES(12c+)
-- TIE 含む Top 3
SELECT * FROM emp
ORDER BY sal DESC
FETCH FIRST 3 ROWS WITH TIES;
-- RANK <= 3 と類似の効果
Rails / Java / Python 対応
Rails ActiveRecord
class Employee < ApplicationRecord
# 部門別 Top N
def self.top_n_per_dept(n)
connection.select_all(<<~SQL).to_a
SELECT * FROM (
SELECT e.*,
ROW_NUMBER() OVER (
PARTITION BY deptno ORDER BY sal DESC
) AS rn
FROM employees e
)
WHERE rn <= #{n.to_i}
SQL
end
# ページング
def self.paginated(page, per_page)
offset = (page - 1) * per_page
connection.select_all(<<~SQL).to_a
SELECT * FROM (
SELECT e.*,
ROW_NUMBER() OVER (ORDER BY id) AS rn
FROM employees e
)
WHERE rn BETWEEN #{offset + 1} AND #{offset + per_page}
SQL
end
# 重複排除
def self.remove_duplicates_by_email
connection.execute(<<~SQL)
DELETE FROM employees WHERE ROWID IN (
SELECT ROWID FROM (
SELECT ROWID,
ROW_NUMBER() OVER (
PARTITION BY email ORDER BY created_at DESC
) AS rn
FROM employees
)
WHERE rn > 1
)
SQL
end
end
Rails 8 系の詳細は Rails 8 アップグレードガイドの記事、find/find_by/where の記事も参照してください。
Java (JDBC)
public List<Employee> getTopNPerDept(Connection conn, int n)
throws SQLException {
String sql = """
SELECT * FROM (
SELECT e.*,
ROW_NUMBER() OVER (
PARTITION BY deptno ORDER BY sal DESC
) AS rn
FROM employees e
)
WHERE rn <= ?
""";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, n);
try (ResultSet rs = ps.executeQuery()) {
List<Employee> result = new ArrayList<>();
while (rs.next()) {
result.add(mapEmployee(rs));
}
return result;
}
}
}
Python (oracledb)
import oracledb
def top_n_per_dept(conn, n):
cursor = conn.cursor()
cursor.execute("""
SELECT deptno, ename, sal, rn FROM (
SELECT deptno, ename, sal,
ROW_NUMBER() OVER (
PARTITION BY deptno ORDER BY sal DESC
) AS rn
FROM emp
)
WHERE rn <= :n
""", [n])
return cursor.fetchall()
def paginate(conn, table, page, per_page, order_by='id'):
offset = (page - 1) * per_page
cursor = conn.cursor()
cursor.execute(f"""
SELECT * FROM (
SELECT t.*, ROW_NUMBER() OVER (ORDER BY {order_by}) AS rn
FROM {table} t
)
WHERE rn BETWEEN :1 AND :2
""", [offset + 1, offset + per_page])
return cursor.fetchall()
実践シナリオ
シナリオ1:スコアランキング(オリンピック方式)
-- ゲームスコアの順位(同点は同順位、次は飛ぶ)
SELECT player_name, score,
RANK() OVER (ORDER BY score DESC) AS ranking
FROM game_scores;
-- 上位 10 位(TIE 含む可能性)
SELECT * FROM (
SELECT player_name, score,
RANK() OVER (ORDER BY score DESC) AS ranking
FROM game_scores
)
WHERE ranking <= 10;
シナリオ2:EC 売上ランキング(順位表方式)
-- 上位 5 種類の売上商品(同売上は同順位、次は詰める)
SELECT product_name, total_sales, dr
FROM (
SELECT product_name, total_sales,
DENSE_RANK() OVER (ORDER BY total_sales DESC) AS dr
FROM sales_summary
)
WHERE dr <= 5;
シナリオ3:Web ページング
-- 第 3 ページ(1 ページ 20 件)
SELECT * FROM (
SELECT p.*, ROW_NUMBER() OVER (ORDER BY posted_at DESC) AS rn
FROM posts p
)
WHERE rn BETWEEN 41 AND 60;
-- 12c+ の書き方
SELECT * FROM posts
ORDER BY posted_at DESC
OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY;
シナリオ4:重複行の削除
-- users テーブルで email 重複、最新の 1 件だけ残す
DELETE FROM users
WHERE ROWID IN (
SELECT ROWID FROM (
SELECT ROWID,
ROW_NUMBER() OVER (
PARTITION BY email ORDER BY created_at DESC
) AS rn
FROM users
)
WHERE rn > 1
);
COMMIT;
シナリオ5:部門別 Top 3 給与
-- 各部門の給与 Top 3(TIE 含む)
SELECT * FROM (
SELECT deptno, ename, sal,
DENSE_RANK() OVER (
PARTITION BY deptno ORDER BY sal DESC
) AS dr
FROM emp
)
WHERE dr <= 3
ORDER BY deptno, sal DESC;
シナリオ6:各カテゴリの最新商品
-- カテゴリ別に最新の 1 商品
SELECT category_id, product_name, created_at
FROM (
SELECT p.*,
ROW_NUMBER() OVER (
PARTITION BY category_id ORDER BY created_at DESC
) AS rn
FROM products p
)
WHERE rn = 1;
シナリオ7:Rails ページング実装
class PostsController < ApplicationController
def index
page = params[:page].to_i.clamp(1, 1000)
per_page = 20
@posts = Post.paginated(page, per_page)
@total = Post.count
@total_pages = (@total.to_f / per_page).ceil
end
end
シナリオ8:Python での分析
import oracledb
import pandas as pd
def analyze_top_earners(conn):
query = """
SELECT deptno, ename, sal, rnk, dr, rn FROM (
SELECT deptno, ename, sal,
RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS dr,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) AS rn
FROM emp
)
WHERE rnk <= 3
ORDER BY deptno, sal DESC
"""
cursor = conn.cursor()
cursor.execute(query)
columns = [d[0] for d in cursor.description]
df = pd.DataFrame(cursor.fetchall(), columns=columns)
return df
シナリオ9:Java Spring での分析
@Repository
public class SalesRankingRepository {
@PersistenceContext
private EntityManager em;
public List<Object[]> getTopSellingByRegion(int topN) {
String sql = """
SELECT * FROM (
SELECT region, product, sales,
DENSE_RANK() OVER (
PARTITION BY region ORDER BY sales DESC
) AS dr
FROM sales_summary
)
WHERE dr <= ?1
""";
return em.createNativeQuery(sql)
.setParameter(1, topN)
.getResultList();
}
}
シナリオ10:Docker Oracle でのテスト
docker exec -it oracle-xe sqlplus scott/tiger <<EOF
CREATE TABLE scores (name VARCHAR2(20), score NUMBER);
INSERT INTO scores VALUES ('Alice', 95);
INSERT INTO scores VALUES ('Bob', 90);
INSERT INTO scores VALUES ('Carol', 90); -- TIE
INSERT INTO scores VALUES ('Dave', 85);
INSERT INTO scores VALUES ('Eve', 80);
COMMIT;
-- 3 関数比較
SELECT name, score,
RANK() OVER (ORDER BY score DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY score DESC) AS dr,
ROW_NUMBER() OVER (ORDER BY score DESC) AS rn
FROM scores;
-- 結果:
-- NAME SCORE RNK DR RN
-- Alice 95 1 1 1
-- Bob 90 2 2 2
-- Carol 90 2 2 3
-- Dave 85 4 3 4 ← RANK は 3 飛ばし、DR は連続
-- Eve 80 5 4 5
EOF
Docker 関連は docker daemon 接続エラーの記事、Docker no space left on device の記事も参照してください。
トラブルシューティング
ORDER BY 忘れ
ORA-30485: missing ORDER BY expression in the window specification
分析関数は ORDER BY 必須。
NULL の位置がバラバラ
明示指定推奨:
ORDER BY sal DESC NULLS LAST
RANK と DENSE_RANK を混同
用途で厳密に選ぶ:
- 「上位 N 位」→ RANK
- 「上位 N の値」→ DENSE_RANK
- 「正確に N 行」→ ROW_NUMBER
PARTITION BY 忘れで全体順位
部門別なら PARTITION BY 必須:
PARTITION BY deptno ORDER BY sal DESC
ページング遅い
インデックス + Top N 最適化:
-- 12c+
FETCH FIRST 20 ROWS ONLY
PostgreSQL からの移行
PG も同じ 3 関数、構文互換性高い。
よくある質問(FAQ)
Q1. RANK と DENSE_RANK の違い
- RANK: TIE で GAP、5000, 3000, 3000, 2500 → 1, 2, 2, 4
- DENSE_RANK: GAP なし → 1, 2, 2, 3
Q2. ROW_NUMBER の TIE 順序
不定、明示的にソートしないと再現不可。
Q3. どれを使うべきか
- 一意番号: ROW_NUMBER
- 順位(オリンピック): RANK
- 順位(順位表): DENSE_RANK
Q4. PARTITION BY なしは?
全体を 1 グループとして扱う。
Q5. NULLS FIRST/LAST
明示推奨、デフォルトは DESC/ASC で異なる。
Q6. Top N の書き方
- ROW_NUMBER + サブクエリ: 全 Oracle バージョン
- FETCH FIRST N ROWS ONLY: 12c+
Q7. WITH TIES
RANK と同等、FETCH FIRST に付ける。
Q8. Rails での使用
raw SQL + connection.select_all。
Q9. Java での使用
PreparedStatement + Native SQL。
Q10. Python での使用
oracledb.cursor.execute。
Q11. パフォーマンス
インデックス設計 + 12c+ FETCH FIRST。
Q12. 予防策
- ORDER BY 必須
- NULLS FIRST/LAST 明示
- PARTITION BY 忘れずに
- 用途に応じて 3 つを使い分け
参考リンク
Oracle 公式
- Oracle Database SQL Language Reference: RANK
- Oracle Database SQL Language Reference: DENSE_RANK
- Oracle Database SQL Language Reference: ROW_NUMBER
- Oracle Database SQL Language Reference: Analytic Functions
まとめ
Oracle RANK / DENSE_RANK / ROW_NUMBER の要点を再整理します。
3 関数の本質
RANK() - TIE で GAP あり (1, 2, 2, 4, 5)
DENSE_RANK() - TIE で GAP なし (1, 2, 2, 3, 4)
ROW_NUMBER() - 必ず一意な連番 (1, 2, 3, 4, 5)
結果比較表
| Sal | RANK | DENSE_RANK | ROW_NUMBER |
|---|---|---|---|
| 5000 | 1 | 1 | 1 |
| 3000 | 2 | 2 | 2 |
| 3000 | 2 | 2 | 3 |
| 2500 | 4 | 3 | 4 |
| 2500 | 4 | 3 | 5 |
| 1000 | 6 | 4 | 6 |
用途別使い分け
| 用途 | 関数 |
|---|---|
| ページング | ROW_NUMBER |
| 重複排除 | ROW_NUMBER |
| 上位 N 位(オリンピック) | RANK |
| 上位 N の値(順位表) | DENSE_RANK |
| 各グループ 1 行 | ROW_NUMBER |
| Median 計算 | DENSE_RANK |
| N 番目の値 | DENSE_RANK |
基本構文
FUNCTION() OVER (
PARTITION BY partition_col -- グループ内順位
ORDER BY sort_col DESC -- ソート順
NULLS LAST -- NULL 位置
)
定番パターン
-- Top N per group
SELECT * FROM (
SELECT t.*, ROW_NUMBER() OVER (
PARTITION BY group_col ORDER BY sort_col DESC
) AS rn FROM tab t
) WHERE rn <= N;
-- ページング
SELECT * FROM (
SELECT t.*, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM tab t
) WHERE rn BETWEEN start_row AND end_row;
-- 重複排除
DELETE FROM tab WHERE ROWID IN (
SELECT ROWID FROM (
SELECT ROWID, ROW_NUMBER() OVER (
PARTITION BY dup_col ORDER BY created_at DESC
) AS rn FROM tab
) WHERE rn > 1
);
姉妹関数
| 関数 | 用途 |
|---|---|
| NTILE(N) | N 分位分割 |
| PERCENT_RANK | 0-1 の比率 |
| CUME_DIST | 累積分布 |
| FIRST_VALUE | 最初の値 |
| LAST_VALUE | 最後の値 |
| LAG | 前行参照 |
| LEAD | 後行参照 |
実務チェックリスト
□ 用途に応じて 3 関数を厳密に使い分け
□ PARTITION BY で グループ内順位
□ ORDER BY 必須
□ NULLS FIRST/LAST 明示
□ Top N per group は ROW_NUMBER + サブクエリ
□ ページングは ROW_NUMBER or FETCH FIRST
□ 重複排除は ROW_NUMBER + PARTITION BY
□ 12c+ は FETCH FIRST WITH TIES 検討
□ インデックスで SORT 回避
□ Rails/Java/Python は raw SQL
これらの知識は、Oracle での SQL 開発・レポート生成・データ分析・ページング・重複排除・BI ツール・Rails/Java/Python アプリ開発など、あらゆる場面で活用できます。本記事をブックマークしておけば、Oracle の 3 大分析関数をプロフェッショナルとして使い分けられるようになります。
本記事は2026年6月時点の情報をもとに、Oracle Database 12c〜23ai での動作確認・公式ドキュメントに基づき作成しています。Oracle のバージョンにより挙動が異なる場合があるため、最新の情報は Oracle 公式ドキュメント(docs.oracle.com)もあわせてご確認ください。
-
前の記事
【完全ガイド】Oracle AWR レポート 読み方 完全ガイド|Load Profile・Wait Event・Top SQL 徹底解説 2026.09.10
-
次の記事
【完全比較】Oracle RANK / DENSE_RANK / ROW_NUMBER の違いと使い分け完全ガイド 2026.09.14
コメントを書く