Oracleでテーブルからデータを取得するとき、「上位N件だけ取得したい」「ページングで10件ずつ表示したい」というケースは非常に多いですよね。
しかしOracleには、MySQLの LIMIT やSQL Serverの TOP のような直感的な構文がなく、バージョンや目的によって使い分けが必要です。特に ROWNUM にはORDER BYとの組み合わせで意図しない結果になる落とし穴もあります。
この記事では、ROWNUM・FETCH FIRST・ROW_NUMBER() の3つの方法を中心に、構文・注意点・パフォーマンス比較・他RDBMSとの対応まで網羅的に解説します。
この記事で学べること
ROWNUM の基本と注意点(ORDER BYとの落とし穴)
- Oracle 12c以降の
FETCH FIRST N ROWS ONLY(推奨)
ROW_NUMBER() ウィンドウ関数による柔軟な件数制限
RANK() / DENSE_RANK() との違い
- パフォーマンス比較と使い分けの指針
- MySQL / SQL Server / PostgreSQL との構文対応表
サンプルデータの準備
この記事では、以下の employees テーブルを使って各方法を解説します。
サンプルテーブル
CREATE TABLE employees (
emp_id NUMBER PRIMARY KEY,
emp_name VARCHAR2(50) NOT NULL,
dept VARCHAR2(30),
salary NUMBER(10,2)
);
INSERT ALL
INTO employees VALUES(1, '田中太郎', '営業部', 450000)
INTO employees VALUES(2, '鈴木花子', '開発部', 520000)
INTO employees VALUES(3, '佐藤一郎', '営業部', 380000)
INTO employees VALUES(4, '高橋美咲', '開発部', 600000)
INTO employees VALUES(5, '山田次郎', '人事部', 410000)
INTO employees VALUES(6, '伊藤三郎', '開発部', 550000)
INTO employees VALUES(7, '渡辺四郎', '営業部', 470000)
INTO employees VALUES(8, '中村五郎', '人事部', 350000)
SELECT * FROM dual;
| EMP_ID |
EMP_NAME |
DEPT |
SALARY |
| 1 |
田中太郎 |
営業部 |
450,000 |
| 2 |
鈴木花子 |
開発部 |
520,000 |
| 3 |
佐藤一郎 |
営業部 |
380,000 |
| 4 |
高橋美咲 |
開発部 |
600,000 |
| 5 |
山田次郎 |
人事部 |
410,000 |
| 6 |
伊藤三郎 |
開発部 |
550,000 |
| 7 |
渡辺四郎 |
営業部 |
470,000 |
| 8 |
中村五郎 |
人事部 |
350,000 |
方法1:ROWNUM で件数を制限する(基本)
ROWNUM はOracleの疑似列(Pseudocolumn)で、行が返される際に自動的に連番が振られます。
基本構文
構文
SELECT 列名 FROM テーブル名
WHERE ROWNUM <= N;
実行例:先頭3件を取得
SQL
SELECT emp_id, emp_name, salary
FROM employees
WHERE ROWNUM <= 3;
実行結果
EMP_ID EMP_NAME SALARY
------ ---------- --------
1 田中太郎 450000
2 鈴木花子 520000
3 佐藤一郎 380000
ポイント:ROWNUM は 1から始まる連番 です。ROWNUM <= N で先頭N件を取得できます。= ではなく <= を使うのが基本です。
ROWNUM の注意点:= や > は使えない
ROWNUM は行が取得される際に順番に振られるため、ROWNUM = 5 や ROWNUM > 3 のような条件は結果が0件になります。
NG例
-- NG: 5行目だけ取得したい → 結果は0件
SELECT * FROM employees
WHERE ROWNUM = 5;
-- NG: 4行目以降を取得したい → 結果は0件
SELECT * FROM employees
WHERE ROWNUM > 3;
なぜ0件になるのか:ROWNUM は行が返される前に評価されます。1行目が取得候補になったとき ROWNUM=1 ですが、条件 ROWNUM=5 を満たさないため破棄されます。破棄されたので次の行もまた ROWNUM=1 から始まり、永遠に条件を満たせません。
ROWNUM + ORDER BY の落とし穴
ROWNUM と ORDER BY を組み合わせる際は要注意です。
間違いパターン:ソートされていないTOP N
NG: 意図しない結果になる
-- 給与TOP3を取得したいが…
SELECT emp_name, salary
FROM employees
WHERE ROWNUM <= 3
ORDER BY salary DESC;
実行結果(意図しない結果)
EMP_NAME SALARY
---------- --------
鈴木花子 520000
田中太郎 450000
佐藤一郎 380000
問題点:SQL の処理順序は WHERE → ORDER BY のため、まずランダムな3件が選ばれ、その後にソートされるだけです。本当の給与TOP3(600000, 550000, 520000)にはなりません。
正しいパターン:サブクエリ + ROWNUM
ソート後にTOP N件を取得するには、サブクエリで先にORDER BYを実行してからROWNUMで制限します。
OK: サブクエリでソート後に件数制限
SELECT * FROM (
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
)
WHERE ROWNUM <= 3;
実行結果(正しい給与TOP3)
EMP_NAME SALARY
---------- --------
高橋美咲 600000
伊藤三郎 550000
鈴木花子 520000
ポイント:内側のサブクエリで ORDER BY を実行し、外側のクエリで ROWNUM を使って件数を制限します。これにより「ソートされたデータの先頭N件」を正しく取得できます。
方法2:FETCH FIRST N ROWS ONLY(Oracle 12c以降・推奨)
Oracle 12c(12.1)から導入された FETCH FIRST 句は、最も直感的でおすすめの方法です。ANSI SQL標準にも準拠しています。
基本構文
構文
SELECT 列名 FROM テーブル名
ORDER BY ソート条件
FETCH FIRST N ROWS ONLY;
実行例:給与TOP3を取得
SQL
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
FETCH FIRST 3 ROWS ONLY;
実行結果
EMP_NAME SALARY
---------- --------
高橋美咲 600000
伊藤三郎 550000
鈴木花子 520000
ROWNUM のようなサブクエリが不要で、ORDER BY との組み合わせも直感的です。
OFFSET … FETCH でページング
OFFSET を組み合わせると、ページネーション(ページング)を簡単に実装できます。
構文
SELECT 列名 FROM テーブル名
ORDER BY ソート条件
OFFSET M ROWS
FETCH NEXT N ROWS ONLY;
| キーワード |
意味 |
OFFSET M ROWS |
先頭からM行スキップする |
FETCH FIRST N ROWS ONLY |
先頭N行を取得(OFFSETなし) |
FETCH NEXT N ROWS ONLY |
OFFSET後のN行を取得 |
FETCH FIRST N PERCENT ROWS ONLY |
全体のN%の行を取得 |
WITH TIES |
同順位の行も含めて返す |
ページング実行例
SQL: 1ページ3件で2ページ目を取得
-- 1ページ目(1-3件目)
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
FETCH FIRST 3 ROWS ONLY;
-- 2ページ目(4-6件目)
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
OFFSET 3 ROWS
FETCH NEXT 3 ROWS ONLY;
-- 3ページ目(7-9件目)
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
OFFSET 6 ROWS
FETCH NEXT 3 ROWS ONLY;
2ページ目の実行結果
EMP_NAME SALARY
---------- --------
田中太郎 450000
山田次郎 410000
佐藤一郎 380000
ページング計算式
- OFFSET = (ページ番号 – 1) x 1ページの表示件数
- FETCH NEXT = 1ページの表示件数
- 例:1ページ10件表示で3ページ目 →
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
WITH TIES:同順位も含めて取得
ROWS ONLY の代わりに WITH TIES を使うと、ORDER BY で同じ値の行も含めて返します。
SQL
-- 給与TOP3(同額の場合も含む)
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
FETCH FIRST 3 ROWS WITH TIES;
ポイント:WITH TIES は ORDER BY 句が必須です。同順位の行が含まれるため、取得件数が指定したN件を超える場合があります。
パーセント指定
全体の何%かを取得することもできます。
SQL
-- 全体の上位25%を取得(8件中2件)
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
FETCH FIRST 25 PERCENT ROWS ONLY;
方法3:ROW_NUMBER() ウィンドウ関数
ROW_NUMBER() はウィンドウ関数(分析関数)で、指定した順序で連番を振ります。最も柔軟な方法であり、グループ別のTOP Nなど複雑な要件にも対応できます。
基本構文
構文
SELECT * FROM (
SELECT 列名,
ROW_NUMBER() OVER(ORDER BY ソート条件) AS rn
FROM テーブル名
)
WHERE rn <= N;
実行例:給与TOP3を取得
SQL
SELECT * FROM (
SELECT emp_name, salary,
ROW_NUMBER() OVER(ORDER BY salary DESC) AS rn
FROM employees
)
WHERE rn <= 3;
実行結果
EMP_NAME SALARY RN
---------- -------- ----
高橋美咲 600000 1
伊藤三郎 550000 2
鈴木花子 520000 3
PARTITION BY:グループ別TOP N
PARTITION BY を使うと、グループ(部門など)ごとのTOP Nを取得できます。これは ROWNUM や FETCH FIRST では実現できない強力な機能です。
SQL: 部門別の給与TOP1を取得
SELECT * FROM (
SELECT emp_name, dept, salary,
ROW_NUMBER() OVER(
PARTITION BY dept
ORDER BY salary DESC
) AS rn
FROM employees
)
WHERE rn = 1;
実行結果
EMP_NAME DEPT SALARY RN
---------- ------ -------- --
高橋美咲 開発部 600000 1
渡辺四郎 営業部 470000 1
山田次郎 人事部 410000 1
ROW_NUMBER でページング
Oracle 11g以前で FETCH FIRST が使えない場合、ROW_NUMBER() でページングを実装します。
SQL: 1ページ3件で2ページ目を取得
SELECT * FROM (
SELECT emp_name, salary,
ROW_NUMBER() OVER(ORDER BY salary DESC) AS rn
FROM employees
)
WHERE rn BETWEEN 4 AND 6;
実行結果(2ページ目)
EMP_NAME SALARY RN
---------- -------- ----
田中太郎 450000 4
山田次郎 410000 5
佐藤一郎 380000 6
RANK() / DENSE_RANK() との違い
ROW_NUMBER() と似たウィンドウ関数に RANK() と DENSE_RANK() があります。これらの違いを理解しておくことが重要です。
3つの関数の違い
同じ値がある場合の番号の振り方が異なります。
SQL: 3つの関数を比較
SELECT
emp_name,
salary,
ROW_NUMBER() OVER(ORDER BY salary DESC) AS row_num,
RANK() OVER(ORDER BY salary DESC) AS rank_num,
DENSE_RANK() OVER(ORDER BY salary DESC) AS dense_num
FROM employees
ORDER BY salary DESC;
給与が同額の社員がいた場合のイメージ:
実行結果イメージ(同額があると仮定)
EMP_NAME SALARY ROW_NUM RANK_NUM DENSE_NUM
---------- -------- ------- -------- ---------
高橋美咲 600000 1 1 1
伊藤三郎 550000 2 2 2
鈴木花子 520000 3 3 3
渡辺四郎 520000 4 3 3
田中太郎 450000 5 5 4
...
| 関数 |
同値の扱い |
欠番 |
用途 |
ROW_NUMBER() |
一意の連番(重複なし) |
なし |
ページング・一意のTOP N |
RANK() |
同値は同順位 |
あり(1,2,2,4…) |
順位付け(同率あり) |
DENSE_RANK() |
同値は同順位 |
なし(1,2,2,3…) |
密な順位付け |
使い分けの指針:「正確にN件取得」したいなら ROW_NUMBER()、「同率順位も含めて取得」したいなら RANK() または DENSE_RANK() を使います。
ROWNUMを使ったページング(Oracle 11g以前)
Oracle 11g以前では FETCH FIRST が使えないため、ROWNUM のネストでページングを実装します。
SQL: ROWNUMで2ページ目を取得(1ページ3件)
SELECT * FROM (
SELECT a.*, ROWNUM AS rnum
FROM (
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
) a
WHERE ROWNUM <= 6 -- 終了行(ページ番号 x 件数)
)
WHERE rnum > 3; -- 開始行((ページ番号-1) x 件数)
注意:ROWNUMのネストは3重になるためSQLが複雑です。Oracle 12c以降では OFFSET ... FETCH または ROW_NUMBER() の使用を推奨します。
パフォーマンス比較
各方法のパフォーマンス特性を比較します。
| 方法 |
内部処理 |
パフォーマンス |
備考 |
ROWNUM |
COUNT STOPKEY |
最速 |
N件見つかった時点で処理停止 |
FETCH FIRST |
WINDOW SORT PUSHED RANK |
高速 |
内部的にROW_NUMBER相当に変換 |
ROW_NUMBER() |
WINDOW SORT |
高速 |
全行にランク付け後フィルタ |
RANK() / DENSE_RANK() |
WINDOW SORT |
高速 |
ROW_NUMBER()と同等 |
パフォーマンスのポイント
ROWNUM(ORDER BYなし)は最速ですが、ソート結果が不要なケースに限定されます
FETCH FIRST はオプティマイザが内部的に最適化するため、ROWNUMサブクエリと同等の性能です
- 大量データのページングでは OFFSET が大きいほど遅くなる(キーセット方式の検討が必要)
- 適切なインデックスがある場合、全方法とも大幅に高速化されます
実行計画の確認方法
どの方法が効率的かは、実行計画で確認できます。
SQL
-- 実行計画を表示
EXPLAIN PLAN FOR
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
FETCH FIRST 3 ROWS ONLY;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
実行計画の例
----------------------------------------------------
| Id | Operation | Name | Rows |
----------------------------------------------------
| 0 | SELECT STATEMENT | | 3 |
|* 1 | VIEW | | 3 |
|* 2 | WINDOW SORT PUSHED RANK| | 8 |
| 3 | TABLE ACCESS FULL | EMPLOYEES | 8 |
----------------------------------------------------
WINDOW SORT PUSHED RANK は、全行をソートせずにTOP Nだけを効率的に処理する最適化です。
他のRDBMSとの比較
件数制限の構文はRDBMSによって大きく異なります。他のDBを併用している場合は対応表を参考にしてください。
TOP N 件取得の構文比較
| RDBMS |
TOP N 構文 |
| Oracle 12c+ |
FETCH FIRST N ROWS ONLY |
| Oracle 11g以前 |
WHERE ROWNUM <= N(サブクエリ必要) |
| MySQL |
LIMIT N |
| PostgreSQL |
LIMIT N または FETCH FIRST N ROWS ONLY |
| SQL Server |
SELECT TOP N ... |
ページング構文の比較
| RDBMS |
ページング構文 |
| Oracle 12c+ |
OFFSET M ROWS FETCH NEXT N ROWS ONLY |
| MySQL |
LIMIT N OFFSET M |
| PostgreSQL |
LIMIT N OFFSET M または OFFSET M FETCH NEXT N ROWS ONLY |
| SQL Server |
OFFSET M ROWS FETCH NEXT N ROWS ONLY |
各RDBMSのサンプルコード
MySQL
-- TOP 3
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3;
-- ページング(2ページ目、1ページ3件)
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3 OFFSET 3;
SQL Server
-- TOP 3
SELECT TOP 3 emp_name, salary
FROM employees
ORDER BY salary DESC;
-- ページング(2ページ目、1ページ3件)
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
OFFSET 3 ROWS
FETCH NEXT 3 ROWS ONLY;
PostgreSQL
-- TOP 3
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3;
-- ページング(2ページ目、1ページ3件)
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3 OFFSET 3;
よくあるエラーと対処法
件数制限のSQLで発生しやすいエラーをまとめます。
| エラー / 問題 |
原因 |
対処法 |
ORA-00933 |
Oracle 11g以前で FETCH FIRST を使用 |
ROWNUMまたはROW_NUMBER()を使う |
ROWNUM = N で0件 |
ROWNUMの仕様(= や > は使えない) |
サブクエリ + ROWNUMまたはROW_NUMBER()を使う |
| ORDER BYと併用で意図しない結果 |
ROWNUMがソート前に評価される |
サブクエリで先にORDER BYを実行 |
ORA-30484 |
ウィンドウ関数をWHERE句に直接使用 |
サブクエリに包んでからWHEREで絞る |
| ページング結果がおかしい |
ORDER BYが一意でなく結果が不安定 |
ORDER BYに一意キーを追加する |
ORA-00933: FETCH FIRST が使えない場合
エラーと対処
-- Oracle 11g以前でこのSQLを実行するとエラー
SELECT * FROM employees
FETCH FIRST 3 ROWS ONLY;
-- ORA-00933: SQLコマンドが正しく終了されていません
-- 対処: ROWNUM を使う
SELECT * FROM employees
WHERE ROWNUM <= 3;
ORA-30484: ウィンドウ関数をWHEREに直接書けない
エラーと対処
-- NG: ウィンドウ関数はWHERE句に直接書けない
SELECT emp_name, salary
FROM employees
WHERE ROW_NUMBER() OVER(ORDER BY salary DESC) <= 3;
-- ORA-30484: ウインドウ関数はここでは使用できません
-- OK: サブクエリに包む
SELECT * FROM (
SELECT emp_name, salary,
ROW_NUMBER() OVER(ORDER BY salary DESC) AS rn
FROM employees
)
WHERE rn <= 3;
ページングの結果が不安定になる問題
注意:ORDER BY に指定した列に同じ値がある場合、ページをまたいで同じ行が表示されたり、表示されない行が出ることがあります。ORDER BYに一意な列(主キーなど)を追加することで、この問題を回避できます。
SQL
-- NG: salaryが同額だと順序が不安定
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY;
-- OK: 一意キー(emp_id)を追加して安定させる
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC, emp_id ASC
OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY;
実務パターン集
実際の業務でよく使われるパターンをまとめます。
パターン1:最新N件のレコードを取得
SQL: 最新の注文5件を取得
SELECT order_id, customer_name, order_date, amount
FROM orders
ORDER BY order_date DESC
FETCH FIRST 5 ROWS ONLY;
パターン2:Webアプリのページング
SQL: 商品一覧の動的ページング
-- :page_num と :page_size はバインド変数
SELECT product_id, product_name, price
FROM products
WHERE category = 'Electronics'
ORDER BY product_name, product_id
OFFSET (:page_num - 1) * :page_size ROWS
FETCH NEXT :page_size ROWS ONLY;
パターン3:各グループの最新1件を取得
SQL: 各顧客の最新注文を取得
SELECT * FROM (
SELECT
customer_id,
order_id,
order_date,
amount,
ROW_NUMBER() OVER(
PARTITION BY customer_id
ORDER BY order_date DESC
) AS rn
FROM orders
)
WHERE rn = 1;
パターン4:重複排除して最新データを取得
SQL: メールアドレスごとの最新ログイン
SELECT * FROM (
SELECT
email,
login_date,
ip_address,
ROW_NUMBER() OVER(
PARTITION BY email
ORDER BY login_date DESC
) AS rn
FROM login_history
)
WHERE rn = 1;
-- 上位3件のログインに限定する場合
-- WHERE rn <= 3;
パターン5:大量ページング時のキーセットページング
OFFSET が大きくなるとパフォーマンスが低下します。そのような場合はキーセットページングが有効です。
SQL: キーセットページング(高速)
-- OFFSETの代わりに前ページの最後のキーを使う
-- 前ページの最後: salary=450000, emp_id=1
SELECT emp_name, salary, emp_id
FROM employees
WHERE (salary, emp_id) < (450000, 1)
ORDER BY salary DESC, emp_id DESC
FETCH FIRST 3 ROWS ONLY;
キーセットページングのメリット
- OFFSETのように先頭からスキャンする必要がなく、常に一定速度
- 100万件目のページでも1ページ目と同じ速度で取得可能
- ただし「前ページの最後のキー」をアプリ側で保持する必要がある
各方法の選び方まとめ
| 用途 |
推奨方法 |
理由 |
| 単純なTOP N取得 |
FETCH FIRST |
シンプルで直感的 |
| ページング |
OFFSET ... FETCH |
標準SQL対応 |
| グループ別TOP N |
ROW_NUMBER() + PARTITION BY |
他の方法では不可 |
| 同順位を含むTOP N |
FETCH ... WITH TIES or RANK() |
同率順位を考慮 |
| Oracle 11g以前 |
サブクエリ + ROWNUM |
唯一の選択肢 |
| 大量ページング(高速) |
キーセットページング |
OFFSETの性能劣化を回避 |
よくある質問(FAQ)
Q. OracleでROWNUMを使うときの典型的な落とし穴は何ですか?
A. ROWNUM = N(N>1)という条件は常にFALSEになります。ROWNUMはWHERE句の評価中に増えるため、最初の行がROWNUM=1で条件を通過しなければ次の行は永遠にROWNUM=1になります。そのため「ROWNUM = 5」という条件では結果が0件になります。上位N件はROWNUM <= N、中間の行はサブクエリでSELECT * FROM (SELECT ..., ROWNUM rn FROM ...) WHERE rn BETWEEN 5 AND 10と書きます。
Q. Oracle 12c以降のFETCH FIRST構文とROWNUMのどちらを使うべきですか?
A. Oracle 12c以降ならFETCH FIRST N ROWS ONLYの方が可読性が高く推奨されます。ページネーションもOFFSET N ROWS FETCH NEXT M ROWS ONLYでシンプルに書けます。12c未満の環境ではROWNUMまたはROW_NUMBER()ウィンドウ関数を使います。
Q. ROW_NUMBER()とROWNUMの違いは何ですか?
A. ROWNUMはSQL処理の早い段階(フィルタ前)で割り当てられるため、ORDER BY後の正しい行番号を直接取得できません。ROW_NUMBER()はウィンドウ関数でORDER BYを指定した順序に従って行番号を割り当てます。ソート済みの行番号が必要な場合はROW_NUMBER() OVER (ORDER BY ...)を使います。
まとめ
| 項目 |
内容 |
| ROWNUM |
Oracle全バージョンで使える。ORDER BYと組み合わせる場合はサブクエリ必須 |
| FETCH FIRST(推奨) |
Oracle 12c以降。直感的でANSI SQL準拠。ページングもOFFSETで簡単 |
| ROW_NUMBER() |
最も柔軟。PARTITION BYでグループ別TOP Nにも対応 |
| RANK() / DENSE_RANK() |
同順位を考慮した件数制限が必要な場合に使用 |
| パフォーマンス |
適切なインデックスがあれば全方法とも高速。大量OFFSETにはキーセット方式を検討 |
Oracle 12c以降を使っている場合は、まず FETCH FIRST を検討するのがベストプラクティスです。グループ別のTOP Nが必要な場合は ROW_NUMBER()、Oracle 11g以前では ROWNUM のサブクエリパターンを使いましょう。
いずれの方法でも、ORDER BYに一意キーを含めることで結果の安定性を確保することを忘れないでください。