この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:3
ページ更新者:atom
更新日時:2026-06-11 07:07:02

タイトル: LIMIT, OFFSET の始まりと挙動
SEOタイトル: 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 クエリ発行。