При работе с базами данных часто возникает необходимость не выводить весь набор данных, а получить только определенную его часть — например, первые 10 записей, страницу результатов для пагинации или топ-5 самых дорогих товаров. В SQL для этого используются механизмы ограничения выборки (row limiting) и смещения (offset).
Хотя стандарт SQL:2008 ввел предложения OFFSET ... FETCH FIRST ..., большинство популярных СУБД (MySQL, PostgreSQL, SQLite, SQL Server, Oracle) имеют свои синтаксические особенности. В этой статье подробно разберем, как правильно формировать выборку в разных системах.
Основные концепции
- Ограничение (LIMIT / FETCH / TOP): Задает максимальное количество строк, которое вернет запрос.
- Смещение (OFFSET): Указывает, сколько строк нужно пропустить с начала результирующего набора перед началом возврата данных.
- Порядок выполнения (важно!): Сначала применяется
ORDER BY(сортировка), затемOFFSET(пропуск), и только потомLIMIT / FETCH(взятие нужного количества). БезORDER BYпорядок строк не гарантирован, что делает пагинацию непредсказуемой.
Синтаксис по популярным СУБД
PostgreSQL, SQLite, MySQL (стандартный вариант: LIMIT … OFFSET)
Это наиболее распространенный синтаксис, используемый в PostgreSQL, SQLite и современных версиях MySQL/MariaDB.
SELECT колонки
FROM таблица
WHERE условие
ORDER BY колонка_сортировки
LIMIT количество_строк OFFSET количество_пропускаемых_строк; Пример: Получить 10 товаров, пропустив первые 20 (т.е. 3-я страница при постраничном выводе по 10 шт.).
SELECT * FROM products
ORDER BY price DESC
LIMIT 10 OFFSET 20; Нюанс MySQL/MariaDB: Исторически MySQL поддерживает альтернативный синтаксис
LIMIT offset, count(обратный порядок аргументов!). Например,LIMIT 20, 10означает «пропустить 20, взять 10». СтандартныйLIMIT 10 OFFSET 20также работает и предпочтителен для переносимости.
Microsoft SQL Server (T-SQL): OFFSET … FETCH NEXT
Начиная с версии SQL Server 2012, используется синтаксис, соответствующий стандарту ANSI SQL. Обязательное условие: наличие предложения ORDER BY.
SELECT колонки
FROM таблица
ORDER BY колонка_сортировки
OFFSET пропуск_строк ROWS
FETCH NEXT количество_строк ROWS ONLY; Пример: Тот же запрос на 3-ю страницу.
SELECT * FROM products
ORDER BY price DESC
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY; Ключевые слова ROW/ROWS и FIRST/NEXT являются синонимами и добавлены для читаемости.
Oracle Database
- Oracle 12c и новее: Поддерживает стандартный синтаксис
OFFSET ... FETCH FIRST ...(аналогично PostgreSQL/SQL Server). - Старые версии (до 12c): Использовали псевдоколонку
ROWNUMили аналитические функции (ROW_NUMBER OVER ...) во вложенных подзапросах.
Пример для Oracle 12c+:
SELECT * FROM products
ORDER BY price DESC
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY; SQL Server (старые версии) и Sybase: TOP
В версиях до SQL Server 2012 (а также в Sybase) использовалось ключевое слово TOP сразу после SELECT. Оно не поддерживает OFFSET напрямую, поэтому для пагинации приходилось использовать сложные конструкции с ROW_NUMBER.
-- Только первые N строк (без смещения)
SELECT TOP 10 * FROM products
ORDER BY price DESC; Сводная таблица отличий
| СУБД | Ключевые слова | Порядок аргументов (LIMIT) | Требуется ORDER BY |
|---|---|---|---|
| PostgreSQL | LIMIT ... OFFSET ... | LIMIT count OFFSET skip | Рекомендуется (для стабильности) |
| SQLite | LIMIT ... OFFSET ... | LIMIT count OFFSET skip | Рекомендуется |
| MySQL / MariaDB | LIMIT ... OFFSET ... или LIMIT offset, count | Оба варианта | Рекомендуется |
| SQL Server (2012+) | OFFSET .;. ROWS FETCH NEXT ... ROWS ONLY | N/A (отдельные клаузылы) | Обязательно |
| Oracle (12c+) | OFFSET ... ROWS FETCH FIRST ... ROWS ONLY | N/A | Обязательно |
| SQL Server (до 2012) | TOP (N) | Только лимит | Рекомендуется |
Практические сценарии использования
Пагинация (By-page navigation)
Самый частый кейс — вывод данных страницами. Формула расчета OFFSET:
OFFSET = (Номер_страницы ⸺ 1) * Размер_страницы
Пример для страницы №3 при размере 10:
- PostgreSQL/MySQL/SQLite:
LIMIT 10 OFFSET 20 - SQL Server/Oracle:
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
Получение «Топ-N» записей
Например, 5 самых дорогих товаров. Здесь OFFSET не нужен (или равен 0).
-- PostgreSQL, MySQL, SQLite
SELECT * FROM products ORDER BY price DESC LIMIT 5;
-- SQL Server, Oracle 12c+
SELECT * FROM products ORDER BY price DESC FETCH FIRST 5 ROWS ONLY;
-- SQL Server (старый стиль)
SELECT TOP 5 * FROM products ORDER BY price DESC; Пропуск первых N записей (без лимита)
Иногда нужно просто отбросить или первые некорректные строки.
-- Пропустить 1 строку, взять все остальные
-- PostgreSQL, SQLite (LIMIT -1 или очень большое число означает "все")
SELECT * FROM logs ORDER BY id LIMIT -1 OFFSET 1;
-- MySQL
SELECT * FROM logs ORDER BY id LIMIT 1###09551615 OFFSET 1;
-- SQL Server / Oracle
SELECT * FROM logs ORDER BY id OFFSET 1 ROWS; Важные нюансы производительности и корректности
- Всегда используйте ORDER BY. Без явной сортировки СУБД может возвращать строки в произвольном порядке (например, в порядке вставки или физического хранения), что приведет к «прыгающей» пагинации и дубликатам на границах страниц.
- Индексы. Для эффективной работы
OFFSET ... LIMITкритически важно наличие индекса по полю(ям) вORDER BY. Без индекса СУБД вынуждена отсортировать всю таблицу (Full Sort), а затем отброситьOFFSETстрок, что крайне медленно на больших таблицах. - Проблема больших OFFSET (Deep Pagination). При запросе глубоких страниц (например,
OFFSET 1000000 LIMIT 10) СУБД все равно должна прочитать и отсортировать миллион строк, чтобы их отбросить.- Решение (Keyset Pagination / Seek Method): Вместо
OFFSETиспользовать условиеWHERE id > last_seen_id ORDER BY id LIMIT 10. Это работает мгновенно за счет индекса.
- Решение (Keyset Pagination / Seek Method): Вместо
- Стабильность сортировки. Если в
ORDER BYесть одинаковые значения (например, несколько товаров с одинаковой ценой), порядок между ними не определен. Добавьте уникальное поле-тайбрейкер:ORDER BY price DESC, id ASC. - OFFSET 0. В PostgreSQL и SQLite
OFFSET 0эквивалентен отсутствию смещения. В SQL Server/Oracle это также валидно. - LIMIT … OFFSET … — для PostgreSQL, MySQL, SQLite.
- OFFSET … FETCH NEXT … — для SQL Server (2012+), Oracle (12c+), стандарт ANSI SQL.
- TOP N — для старых версий SQL Server / Sybase (только для простого ограничения без пагинации).
Примеры для конкретных СУБД (шпаргалка)
PostgreSQL / SQLite
-- Первые 5
SELECT * FROM users ORDER BY created_at DESC LIMIT 5;
-- Страница 2 (по 10 шт.)
SELECT * FROM users ORDER BY created_at DESC LIMIT 10 OFFSET 10;
-- Только смещение (все, кроме первых 3)
SELECT * FROM users ORDER BY id LIMIT -1 OFFSET 3; -- SQLite/Postgres MySQL / MariaDB
-- Стандартный (рекомендуемый)
SELECT * FROM users ORDER BY created_at DESC LIMIT 10 OFFSET 20;
-- Старый стиль (offset, limit)
SELECT * FROM users ORDER BY created_at DESC LIMIT 20, 10; Microsoft SQL Server (2012+)
-- Первые 5
SELECT * FROM users ORDER BY created_at DESC
OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY;
-- Пагинация
DECLARE @PageNum INT = 3, @PageSize INT = 10;
SELECT * FROM users ORDER BY created_at DESC
OFFSET (@PageNum ⸺ 1) * @PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY; Oracle Database (12c+)
SELECT * FROM users ORDER BY created_at DESC
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY; Получение выборки из результата запроса — базовая, но критически важная операция. Главное правило: всегда комбинируйте ограничение (LIMIT/FETCH/TOP) с детерминированной сортировкой (ORDER BY).
Выбирайте синтаксис в зависимости от вашей СУБД:
Для высоконагруженных систем с глубокой пагинацией рассмотрите отказ от OFFSET в пользу Keyset Pagination (фильтрации по курсору/ID последней просмотренной записи).