タイトル: LIMIT 句
SEOタイトル: SQL LIMIT 句完全ガイド — ページネーションと挙動
| この記事の要点 |
|
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 / MariaDB | LIMIT 10 OFFSET 20 | LIMIT 20, 10 (OFFSET, LIMIT) |
| PostgreSQL | LIMIT 10 OFFSET 20 | FETCH FIRST 10 ROWS ONLY |
| SQLite | LIMIT 10 OFFSET 20 | LIMIT 20, 10 |
| SQL Server 2012+ | OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY | TOP 10 (OFFSET 不可) |
| Oracle 12c+ | OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY | 11g 以前は ROWNUM + サブクエリ |
| Db2 | FETCH 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 () もよく使われる。