17.

SQL LIMIT / OFFSET の挙動と境界(0 始まり、ORDER BY 必須の理由)

編集
この記事の要点
  • OFFSET は 0 始まり。1 件目をスキップするなら OFFSET 1
  • LIMIT N OFFSET M = M 件スキップして N 件取得 = (M+1) 件目から N 件
  • ORDER BY 無し LIMIT は結果順が保証されない → ページング必須
  • MySQL の LIMIT M, NLIMIT N OFFSET M と同じ(順序逆なので注意)
  • 大きな OFFSET は遅い → Seek 方式WHERE id > ?)に切替

LIMIT と OFFSET の基本

SQL で「先頭から N 件取得」「途中から N 件取得」を行うための定番構文。

-- 先頭 10 件
SELECT * FROM users ORDER BY id LIMIT 10;

-- 11-20 件目(10 件スキップして 10 件)
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 10;

-- MySQL 独自構文(オフセット, 件数の順)
SELECT * FROM users ORDER BY id LIMIT 10, 10;
-- → LIMIT 10 OFFSET 10 と同じ意味

OFFSET は 0 始まり

取得される行説明
LIMIT 51〜5 件目OFFSET 省略 = 0 と同じ
LIMIT 5 OFFSET 01〜5 件目明示的に 0
LIMIT 5 OFFSET 12〜6 件目1 件スキップ
LIMIT 5 OFFSET 56〜10 件目5 件スキップ
LIMIT 5 OFFSET 100101〜105 件目100 件スキップ

ORDER BY が無いと結果が不定

LIMIT は順序を保証しません。ORDER BY を付けないと、同じクエリでも別の行が返ることがあります(特に並列実行・インデックス変更時)。

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

-- ✅ 必ず ORDER BY を併用
SELECT * FROM users ORDER BY id LIMIT 10;
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 10;

-- ORDER BY の値が重複する場合は第二キーで安定化
SELECT * FROM users ORDER BY created_at DESC, id LIMIT 10;

ページング計算式

件数 (per_page) = 20
ページ番号 (page) = 1 始まり想定

OFFSET = (page - 1) * per_page

ページ 1: OFFSET 0
ページ 2: OFFSET 20
ページ 3: OFFSET 40
...

SQL:
SELECT * FROM users
ORDER BY id
LIMIT 20 OFFSET (page - 1) * 20

各 DB の構文差

DB構文
MySQL / MariaDBLIMIT N OFFSET M or LIMIT M, N(要注意)
PostgreSQLLIMIT N OFFSET M 標準
SQLiteLIMIT N OFFSET M or LIMIT M, N
SQL ServerOFFSET M ROWS FETCH NEXT N ROWS ONLY(要 ORDER BY)
Oracle 12c+OFFSET M ROWS FETCH NEXT N ROWS ONLY
Oracle 11g 以前サブクエリ + ROWNUM

MySQL LIMIT 10, 5 の罠

MySQL 独自構文は「オフセット, 件数」の順。標準の LIMIT 件数 OFFSET オフセット順序が逆なので、両方知っているとミスしやすい:

-- どちらも 11-15 件目
SELECT * FROM users ORDER BY id LIMIT 5 OFFSET 10;   -- 標準
SELECT * FROM users ORDER BY id LIMIT 10, 5;          -- MySQL 独自

-- ❌ よくあるバグ: 順序を逆に書く
SELECT * FROM users ORDER BY id LIMIT 5, 10;          -- 6-15 件目 (10 件)
-- → 「先頭 5 件オフセット 10」のつもりが「5 件オフセット → 10 件取得」

パフォーマンス: 大きな OFFSET は遅い

OFFSET 1000000 のような大きな値は、内部的に 100 万件読み飛ばす処理になり、ページが進むほど遅くなります:

-- ❌ 遅い: 100 万行スキャン
SELECT * FROM logs ORDER BY id LIMIT 20 OFFSET 1000000;

-- ✅ Seek 方式(カーソルベース)
-- 前ページの最後の id を覚えておく
SELECT * FROM logs
WHERE id > 1000000   -- 前ページの最後の id
ORDER BY id
LIMIT 20;

-- 降順の場合
SELECT * FROM logs
WHERE id < 5000000
ORDER BY id DESC
LIMIT 20;

Laravel での書き方

// Eloquent
User::orderBy('id')->skip(20)->take(10)->get();
User::orderBy('id')->offset(20)->limit(10)->get();

// 自動ページング
$users = User::orderBy('id')->paginate(20);
// → ?page=2 でページ 2 取得

// Seek 方式(cursor pagination, Laravel 8+)
$users = User::orderBy('id')->cursorPaginate(20);
// → 大量データでも安定パフォーマンス

FAQ

Q: LIMIT 0 は何が返る?
A: 空の結果セット。クエリのスキーマ確認には使えます。

Q: LIMIT -1 は?
A: 多くの DB でエラー。MySQL では「無制限扱い」になるパターンも報告されているが規格外で非推奨。

Q: COUNT(*) と LIMIT を併用したい
A: SELECT FOUND_ROWS()(MySQL)か、別クエリで COUNT(*)。Laravel paginate() は内部で 2 クエリ発行。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. ダウンロード&インストール方法(Windows)
  2. インストール方法(Linux)
  3. コマンド一覧
  4. SQL
  5. データ型
  6. 関数
  7. 管理ツール
  8. 設定
  9. パフォーマンスチューニング関連
  10. エクスポートおよびインポート
  11. エラー&トラブル
  12. 文字コードの確認
  13. 実行中の SQL の状態確認およびプロセスキルの方法
  14. パスワードの無効化設定
  15. root ユーザーの初期パスワード確認方法
  16. rootユーザーのパスワード変更方法
  17. LIMIT, OFFSET の始まりと挙動
  18. mysqlのバージョン確認方法
  19. 実行計画の表示方法
  20. レプリケーションのステータス確認方法
  21. 中央値の導き方(バージョン8未満)
  22. 階層SQL(バージョン8未満)
  23. パーセンタイルの導き方
  24. 特定スキーマの全テーブルの全カラム情報を取得する方法
  25. MySQLで文字列の置換をする方法