SQL ServerでMySQLやPostgreSQLのように LIMIT 10 と書こうとして、エラーになった経験がある人は多いはずです。SQL ServerにはLIMIT句がないため、先頭N件の取得にはTOP、ページングにはOFFSET FETCH、グループごとの上位N件にはROW_NUMBERを使います。
この記事では、SQL Serverで件数を絞る書き方を、単なる構文変換ではなく、ORDER BYの必要性、ページングの安定性、インデックス設計、遅くなりやすい書き方まで含めて整理します。
SQL Serverで
LIMIT相当を行うなら、先頭N件はSELECT TOP (N)、ページングはORDER BY ... OFFSET ... FETCH NEXT ...、グループごとの上位N件はROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)を使います。どの方法でも、結果を安定させたいならORDER BYを明示するのが基本です。SQL ServerにLIMIT句はない
SQL Serverでは、次のようなLIMIT句は使えません。
SELECT OrderID, OrderDate, TotalAmount FROM dbo.Orders ORDER BY OrderDate DESC LIMIT 10;
この書き方はMySQLやPostgreSQLではよく使われますが、SQL Serverでは構文エラーになります。SQL Serverでは、目的に応じて次のように書き換えます。
| 目的 | SQL Serverで使う構文 | 主な用途 |
|---|---|---|
| 先頭N件を取得 | TOP (N) |
最新10件、売上上位5件など |
| ページング | OFFSET ... FETCH NEXT ... |
一覧画面の2ページ目、3ページ目 |
| グループごとの上位N件 | ROW_NUMBER() |
顧客ごとの最新注文、部署ごとの上位者 |
| 古い互換構文 | SET ROWCOUNT |
基本的には新規利用を避ける |
LIMITからの書き換え早見表
MySQLやPostgreSQLのLIMITをSQL Serverへ直すなら、まずは次の対応で考えます。単純な件数指定はTOP、スキップを含むページングはOFFSET FETCHです。
| やりたいこと | MySQL/PostgreSQL | SQL Server |
|---|---|---|
| 先頭10件 | ORDER BY CreatedAt DESC LIMIT 10 |
SELECT TOP (10) ... ORDER BY CreatedAt DESC |
| 20件スキップして10件 | ORDER BY CreatedAt DESC LIMIT 10 OFFSET 20 |
ORDER BY CreatedAt DESC OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY |
| 1件だけ取得 | LIMIT 1 |
TOP (1) |
| グループごとの上位N件 | DBごとに書き方が分かれやすい | ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) |
-- MySQL / PostgreSQL SELECT OrderID, OrderDate, TotalAmount FROM Orders ORDER BY OrderDate DESC LIMIT 10 OFFSET 20; -- SQL Server SELECT OrderID, OrderDate, TotalAmount FROM dbo.Orders ORDER BY OrderDate DESC, OrderID DESC OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
FETCH NEXTを使う場合は、先頭ページでもOFFSET 0 ROWSを書きます。また、ページングでは同じ日時の行が複数あると結果が前後しやすいため、OrderIDのような一意性のある列をORDER BYに足すのが安全です。
汎用SQLとしてのLIMIT句は、SQLのLIMIT句の使い方でも整理しています。この記事ではSQL Serverに絞って、実務で迷いやすい置き換え方を扱います。
先頭N件はTOPを使う
SQL Serverで単純に先頭N件を取得するなら、TOPを使います。Microsoft Learnでも、TOPは結果セットの行数を指定数または割合に制限する句として説明されています。
SELECT TOP (10)
OrderID,
OrderDate,
TotalAmount
FROM dbo.Orders
ORDER BY OrderDate DESC;
この例では、注文日の新しい順に10件を取得します。ポイントは、TOPだけでなくORDER BYも指定していることです。ORDER BYがなければ、どの10件が返るかを安定して期待できません。
| 書き方 | 意味 | 実務での評価 |
|---|---|---|
SELECT TOP (10) ... ORDER BY OrderDate DESC |
注文日の新しい順に10件 | 推奨 |
SELECT TOP (10) ... |
任意の10件 | 確認用途以外では避けたい |
SELECT TOP 10 ... |
古い書き方として動くことはある | 括弧付きのTOP (10)が無難 |
TOPはORDER BYと組み合わせる
TOPを使うときにもっとも多い失敗は、「先頭10件」と言いながら並び順を指定していないことです。テーブルに登録された順、主キー順、クラスタ化インデックス順に見えることがあっても、それは仕様として保証された結果ではありません。
-- どの10件かを安定して説明できない
SELECT TOP (10)
OrderID,
OrderDate,
TotalAmount
FROM dbo.Orders;
「最新10件」「金額が高い10件」「IDが小さい10件」のように、人間が意味を理解できる件数指定にするなら、必ず並び順を書きます。
-- 最新10件を取得する
SELECT TOP (10)
OrderID,
OrderDate,
TotalAmount
FROM dbo.Orders
ORDER BY OrderDate DESC, OrderID DESC;
同じOrderDateの行が複数あるなら、OrderIDのような一意性のある列も並び順に追加すると、結果がより安定します。
同順位も含めるならWITH TIESを使う
売上上位10件のようなランキングでは、10位と同じ値の行も含めたいことがあります。その場合はWITH TIESを使います。
SELECT TOP (10) WITH TIES
ProductID,
ProductName,
SalesAmount
FROM dbo.ProductSales
ORDER BY SalesAmount DESC;
WITH TIESを使うと、最後の順位と同じORDER BY値を持つ行も返ります。そのため、TOP (10)と書いていても、結果が10行を超えることがあります。
ページングはOFFSET FETCHを使う
一覧画面のページングでは、TOPよりもOFFSET FETCHが素直です。SQL ServerではORDER BY句にOFFSETとFETCHを付けて、スキップする行数と取得する行数を指定します。
DECLARE @PageNumber int = 2;
DECLARE @PageSize int = 20;
SELECT
OrderID,
OrderDate,
CustomerID,
TotalAmount
FROM dbo.Orders
ORDER BY OrderDate DESC, OrderID DESC
OFFSET (@PageNumber - 1) * @PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY;
2ページ目を20件ずつ表示するなら、20行をスキップして次の20行を取得します。Microsoft Learnでも、ページング用途ではOFFSETとFETCHを使う方法が説明されています。
OFFSET FETCHではORDER BYが必須
OFFSET FETCHはORDER BY句の一部として書きます。そのため、ページングでは並び順を必ず決める必要があります。さらに、同じ並び順の値が複数ある場合は、ページをまたいで行が前後することがあります。
-- OrderDateだけだと同じ日時の行が並び替わる可能性がある ORDER BY OrderDate DESC -- 一意性のある列を足すと安定しやすい ORDER BY OrderDate DESC, OrderID DESC
ページング結果を安定させたいなら、ORDER BYに一意性を持たせるのが基本です。公式ドキュメントでも、安定したページングには一意性が保証される列または列の組み合わせが必要だと説明されています。
深いページングは遅くなりやすい
OFFSET 100000 ROWS FETCH NEXT 20 ROWS ONLYのような深いページングは、SQL Serverが大量の行を読み飛ばす必要があり、遅くなりやすいです。とくにORDER BYに合うインデックスがない場合、Sortや大きな読み取りが発生します。
SELECT
OrderID,
OrderDate,
TotalAmount
FROM dbo.Orders
ORDER BY OrderDate DESC, OrderID DESC
OFFSET 100000 ROWS
FETCH NEXT 20 ROWS ONLY;
このような一覧が重要なら、ORDER BYに合わせた複合インデックスを検討します。たとえば注文日の降順一覧なら、次のようなインデックスが候補になります。
CREATE NONCLUSTERED INDEX IX_Orders_OrderDate_OrderID ON dbo.Orders (OrderDate DESC, OrderID DESC) INCLUDE (CustomerID, TotalAmount);
列順の考え方は、SQL Serverの複合インデックスの列順でも詳しく整理しています。実際に効いているかは、SQL Serverの実行計画の見方でSort、Index Scan、Index Seek、Key Lookupを確認します。
次ページ方式ならキーセットページングも候補
「前へ」「次へ」だけの画面なら、ページ番号ではなく最後に表示したキーを使う方法もあります。これをキーセットページング、またはシーク方式のページングと呼ぶことがあります。
DECLARE @LastOrderDate datetime2 = '2026-05-01T10:30:00';
DECLARE @LastOrderID int = 12345;
SELECT TOP (20)
OrderID,
OrderDate,
CustomerID,
TotalAmount
FROM dbo.Orders
WHERE
OrderDate < @LastOrderDate
OR (OrderDate = @LastOrderDate AND OrderID < @LastOrderID)
ORDER BY OrderDate DESC, OrderID DESC;
任意のページ番号へ直接移動する用途には向きませんが、深いページへ進むほど遅くなる問題を避けやすいのが利点です。この場合も、ORDER BYとWHEREに合うインデックスが重要です。
グループごとの上位N件はROW_NUMBERを使う
TOPは全体の上位N件を取る構文です。顧客ごとの最新注文、カテゴリごとの売上上位3件のように、グループごとの上位N件を取りたい場合はROW_NUMBERを使います。
WITH RankedOrders AS (
SELECT
OrderID,
CustomerID,
OrderDate,
TotalAmount,
ROW_NUMBER() OVER (
PARTITION BY CustomerID
ORDER BY OrderDate DESC, OrderID DESC
) AS rn
FROM dbo.Orders
)
SELECT
OrderID,
CustomerID,
OrderDate,
TotalAmount
FROM RankedOrders
WHERE rn <= 3
ORDER BY CustomerID, rn;
ROW_NUMBERでは、OVER句のORDER BYで番号を振る順番を決めます。同じ値が並ぶ場合は、OrderIDのような列を足して順番を安定させます。
UPDATEやDELETEでTOPを使うときの注意
UPDATE TOP (10)やDELETE TOP (10)は書けますが、そのままだとどの10件が対象になるかを説明しにくいです。古いデータから10件削除する、IDの小さい順に10件更新する、という意図があるなら、サブクエリでORDER BYを指定して対象行を決めます。
DELETE FROM dbo.WorkQueue
WHERE QueueID IN (
SELECT TOP (100) QueueID
FROM dbo.WorkQueue
WHERE Status = 'Done'
ORDER BY CompletedAt ASC, QueueID ASC
);
大量削除を小分けにする場合も、毎回同じ行を選ばないように、条件と並び順を明確にします。ロックやブロッキングが心配な場合は、SQL Serverのテーブルロック確認も合わせて確認してください。
SET ROWCOUNTは基本的に使わない
古いSQL Serverのコードでは、SET ROWCOUNTで取得行数を制限していることがあります。ただし新しく書くなら、SELECTではTOPまたはOFFSET FETCHを使うほうが明確です。公式ドキュメントでも、行数制限にはSET ROWCOUNTよりTOPやOFFSET FETCHが推奨されています。
-- 新規コードでは避けたい SET ROWCOUNT 10; SELECT OrderID, OrderDate FROM dbo.Orders ORDER BY OrderDate DESC; SET ROWCOUNT 0; -- 意図が明確 SELECT TOP (10) OrderID, OrderDate FROM dbo.Orders ORDER BY OrderDate DESC;
RDBMS別の書き方比較
同じ「10件だけ取得」でも、RDBMSごとに書き方が違います。移植時は、構文だけでなく並び順の扱いも確認します。
| RDBMS | 先頭10件 | 20件スキップして10件 |
|---|---|---|
| SQL Server | SELECT TOP (10) ... ORDER BY ... |
ORDER BY ... OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY |
| MySQL | ORDER BY ... LIMIT 10 |
ORDER BY ... LIMIT 10 OFFSET 20 |
| PostgreSQL | ORDER BY ... LIMIT 10 |
ORDER BY ... LIMIT 10 OFFSET 20 |
| Oracle 12c以降 | FETCH FIRST 10 ROWS ONLY |
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY |
SQL Serverでは、LIMITをTOPへ機械的に置換するだけでは足りないことがあります。ページングならOFFSET FETCH、グループ別上位ならROW_NUMBERというように、目的から選びます。
よくあるエラーと原因
| 症状 | 原因 | 修正 |
|---|---|---|
LIMITで構文エラー |
SQL ServerにLIMIT句がない |
TOPまたはOFFSET FETCHへ書き換える |
| ページング結果が毎回ずれる | ORDER BYの一意性が弱い |
主キーなどを並び順に追加する |
| 深いページが遅い | 大量行をスキップしている | インデックス、キーセットページング、検索条件を見直す |
TOPなのに想定外の行が返る |
ORDER BYがない |
意味のある並び順を明示する |
| グループごとの上位が取れない | TOPは全体上位になる |
ROW_NUMBERを使う |
性能確認で見るポイント
件数を絞っているのに遅い場合は、実行計画と読み取り量を確認します。TOPやFETCH NEXTで返す行数が少なくても、並び替えや読み飛ばしのために大量の行を読んでいることがあります。
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT
OrderID,
OrderDate,
CustomerID,
TotalAmount
FROM dbo.Orders
ORDER BY OrderDate DESC, OrderID DESC
OFFSET 20000 ROWS
FETCH NEXT 20 ROWS ONLY;
SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;
見るポイントは、Sortが重くないか、Index Scanで広く読んでいないか、Key Lookupが大量に発生していないか、logical readsが増えすぎていないかです。インデックスが使われない原因や、カバリングインデックスも合わせて確認すると、改善方針を立てやすくなります。
使い分けチェックリスト
| やりたいこと | 使う構文 | 注意点 |
|---|---|---|
| 最新10件を出したい | TOP (10) |
ORDER BYを必ず書く |
| 2ページ目、3ページ目を出したい | OFFSET FETCH |
一意性のある並び順にする |
| 次へボタンだけで進めたい | キーセットページング | 最後に表示したキーを保持する |
| 顧客ごとの最新3件を出したい | ROW_NUMBER |
PARTITION BYでグループを切る |
| 大量削除を小分けにしたい | TOP + サブクエリ |
対象行の順序を明確にする |
よくある質問
SQL ServerでLIMIT 1の代わりは何ですか?
SELECT TOP (1) ... ORDER BY ...です。1件だけでよい場合でも、どの1件かが重要ならORDER BYを書きます。
OFFSET FETCHは何バージョンから使えますか?
SQL Server 2012以降で使えます。古い環境ではROW_NUMBERを使ったページングが選択肢になります。
TOPとOFFSET FETCHを同じSELECTで使えますか?
同じクエリ式の中でTOPとOFFSET FETCHは組み合わせられません。先頭N件ならTOP、ページングならOFFSET FETCHを選びます。
ORDER BYがないTOPは本当にダメですか?
動作確認や任意のサンプル取得なら使うことはあります。ただし、最新、最大、最小、先着順などの意味を持たせるならORDER BYが必要です。
ROW_NUMBERとRANKの違いは何ですか?
ROW_NUMBERは同順位でも連番を振ります。同じ点数を同順位として扱いたいランキングでは、RANKやDENSE_RANKも検討します。
参考
TOP (Transact-SQL) – Microsoft Learn
SELECT – ORDER BY clause (Transact-SQL) – Microsoft Learn
ROW_NUMBER (Transact-SQL) – Microsoft Learn
まとめ
SQL ServerにはLIMIT句がありません。先頭N件を取得するならTOP、ページングならOFFSET FETCH、グループごとの上位N件ならROW_NUMBERを使います。
どの構文でも、結果を安定させる鍵はORDER BYです。さらに、ページングが遅い場合は、実行計画、Sort、logical reads、並び順に合うインデックスを確認しながら改善しましょう。

