タイトル: LIMIT, OFFSET の始まりと挙動
SEOタイトル: SQL LIMIT / OFFSET の挙動と境界(0 始まり、ORDER BY 必須の理由)
| この記事の要点 |
|
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 5 | 1〜5 件目 | OFFSET 省略 = 0 と同じ |
LIMIT 5 OFFSET 0 | 1〜5 件目 | 明示的に 0 |
LIMIT 5 OFFSET 1 | 2〜6 件目 | 1 件スキップ |
LIMIT 5 OFFSET 5 | 6〜10 件目 | 5 件スキップ |
LIMIT 5 OFFSET 100 | 101〜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 / MariaDB | LIMIT N OFFSET M or LIMIT M, N(要注意) |
| PostgreSQL | LIMIT N OFFSET M 標準 |
| SQLite | LIMIT N OFFSET M or LIMIT M, N |
| SQL Server | OFFSET 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 クエリ発行。