1.

SQL LIMIT 句完全ガイド — ページネーションと挙動

編集
この記事の要点
  • LIMIT N は先頭 N 件を取得する句。MySQL/PostgreSQL/SQLite で共通
  • LIMIT N OFFSET M は M 件スキップして N 件取得。1 ページ 10 件なら 2 ページ目は LIMIT 10 OFFSET 10
  • MySQL の LIMIT M, N順序が逆 (OFFSET, LIMIT)。読み間違い注意
  • SQL Server は TOP N または OFFSET ... FETCH NEXT、Oracle 12c+ は FETCH FIRST N ROWS ONLY
  • ORDER BY と必ずセット。順序を指定しないと結果が不定(同じクエリで毎回違う)
  • 大きな OFFSET は遅い。10000 ページ目は数万行スキャン。「Seek 方式 (WHERE id > ?)」で改善

LIMIT 句とは

SELECT クエリの結果行数を制限する句です。ページネーション・サンプル取得・トップ N 表示などで多用します。

-- 先頭 10 件
SELECT id, name FROM users
ORDER BY created_at DESC
LIMIT 10;

-- 11~20 件目(2 ページ目、1 ページ 10 件)
SELECT id, name FROM users
ORDER BY created_at DESC
LIMIT 10 OFFSET 10;

DB 別の構文

DB標準古い方言
MySQL / MariaDBLIMIT 10 OFFSET 20LIMIT 20, 10 (OFFSET, LIMIT)
PostgreSQLLIMIT 10 OFFSET 20FETCH FIRST 10 ROWS ONLY
SQLiteLIMIT 10 OFFSET 20LIMIT 20, 10
SQL Server 2012+OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLYTOP 10 (OFFSET 不可)
Oracle 12c+OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY11g 以前は ROWNUM + サブクエリ
Db2FETCH FIRST 10 ROWS ONLY

MySQL の落とし穴: LIMIT M, N の順序

-- これは同じ意味だが書く順が逆!
SELECT * FROM users LIMIT 10 OFFSET 20;   -- 推奨(明示的)
SELECT * FROM users LIMIT 20, 10;         -- MySQL 方言(OFFSET, LIMIT の順)

-- 読み間違えると 10 件取りたいのに 20 件取れる事故になる

結論: LIMIT N OFFSET M 形式を統一して使う。チーム内のレビューで混在を防ぐ。

ページネーションの典型

-- ページ番号 P (1 始まり)、1 ページ S 件
-- → OFFSET = (P - 1) * S
SELECT id, title, created_at
FROM articles
WHERE status = 'published'
ORDER BY created_at DESC, id DESC   -- 安定ソート(タイムスタンプ重複対策)
LIMIT 10 OFFSET 30;                  -- 4 ページ目

ORDER BY 必須の理由

SQL 標準では「ORDER BY を指定しない結果の順序は不定」。LIMIT で先頭 10 件を取るとき、ORDER BY が無いとどの 10 件が返るか毎回違う可能性があります

-- ❌ 危険: 何が返るか不定
SELECT * FROM users LIMIT 10;

-- ✅ ORDER BY 必須
SELECT * FROM users ORDER BY id LIMIT 10;

大きな OFFSET が遅い理由と対策

LIMIT 10 OFFSET 1000000 は「先頭から 100 万 10 件を読み、最初の 100 万件を捨てる」動きです。OFFSET が大きいほど遅くなります。

-- ❌ 遅い: OFFSET が線形に重くなる
SELECT id, title FROM articles
ORDER BY id LIMIT 10 OFFSET 1000000;

-- ✅ Seek 方式(カーソル方式): 前ページ最後の id を WHERE で渡す
SELECT id, title FROM articles
WHERE id > 1000000        -- 前ページの最後の id
ORDER BY id LIMIT 10;
-- → インデックスを使って直接該当位置にジャンプ

Seek 方式(Keyset Pagination) はページ番号ではなく「次へ」「前へ」ナビゲーションに向きます。SNS のタイムライン・無限スクロールはこれが鉄板。

SQL Server / Oracle のケース

-- SQL Server 2012+ (標準準拠)
SELECT id, name FROM users
ORDER BY id
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

-- SQL Server 2008 以前(TOP のみ、OFFSET 不可)
SELECT TOP 10 * FROM users ORDER BY id;

-- 古い SQL Server で OFFSET 風にしたい場合
SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM users
) t WHERE rn BETWEEN 21 AND 30;

-- Oracle 12c+
SELECT id, name FROM users
ORDER BY id
OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY;

-- Oracle 11g 以前
SELECT * FROM (
  SELECT t.*, ROWNUM AS rn FROM (SELECT * FROM users ORDER BY id) t
  WHERE ROWNUM <= 30
) WHERE rn > 20;

UPDATE / DELETE での LIMIT (MySQL)

-- MySQL は UPDATE / DELETE に LIMIT を付けられる
-- バッチで小分けに削除する定番テクニック
DELETE FROM logs WHERE created_at < '2024-01-01' LIMIT 1000;

-- ループで回す(行が無くなったら 0 が返る)
-- アプリ側で while 文

LIMIT 0 の用途

-- 列構造だけ取得(メタデータ確認)
SELECT * FROM users LIMIT 0;
-- → 0 行返るが列名と型は返るので、クライアントが構造を取得できる

FAQ

Q: LIMIT と TOP は同じ?
A: 役割は同じですが、SQL Server の TOP は OFFSET 不可、LIMIT は OFFSET 可。SQL Server 2012+ は OFFSET FETCH を使うほうが標準的。

Q: LIMIT を変数にできない
A: 多くの DB はプリペアドステートメントで LIMIT ? をバインド可。古い MySQL では文字列連結が必要なことも。プログラム側で必ず整数バリデーションしてから埋め込む。

Q: 総件数も同時に取りたい
A: MySQL の SQL_CALC_FOUND_ROWS はもう非推奨。別途 COUNT(*) クエリを投げるのが速い (インデックスがあれば)。Window 関数の COUNT(*) OVER () もよく使われる。

編集
Post Share
子ページ

子ページはありません

同階層のページ

同階層のページはありません