Урок курса

ORDER BY и LIMIT / OFFSET

SQL с нуля: бесплатный курс с практикой

WHERE научил нас отбирать нужные строки. Теперь встаёт следующий вопрос: в каком порядке эти строки появятся и как показать только часть из них? Именно это делают ORDER BY, LIMIT и OFFSET.

ORDER BY: направление сортировки ASC и DESC, сортировка по нескольким столбцам

Без ORDER BY СУБД возвращает строки в том порядке, в котором ей удобно их извлечь, — и этот порядок может меняться от запроса к запросу. ORDER BY фиксирует порядок явно.

Проще всего это видно на одном столбце:

SELECT title, price FROM products ORDER BY price;

Здесь строки выстроятся от наименьшей цены к наибольшей. Это поведение по умолчанию — восходящий порядок, ASC. Написать ORDER BY price ASC означает ровно то же самое; ключевое слово ASC просто делает намерение явным, что бывает полезно для читаемости кода в команде.

Чтобы развернуть порядок, добавляют DESC:

SELECT title, price FROM products ORDER BY price DESC;

Теперь первой строкой окажется самый дорогой товар. DESC сортирует значения в обратном направлении. Взаимный порядок строк с одинаковым значением остаётся неопределённым, если не добавить следующий критерий в ORDER BY.

По-настоящему интересный случай — несколько столбцов. Представьте таблицу orders со столбцами customer_id и amount. Нужно увидеть заказы, сгруппированные по покупателю, а внутри каждого покупателя — от самого дорогого к самому дешёвому:

SELECT customer_id, amount
FROM orders
ORDER BY customer_id ASC, amount DESC;

СУБД сначала сортирует по customer_id — все заказы одного покупателя собираются вместе. Когда значения customer_id совпадают, вступает второй критерий: amount DESC. Направление задаётся каждому столбцу отдельно; если написать просто ORDER BY customer_id, amount, оба столбца будут ASC.

Важна последовательность: первый столбец в списке — главный приоритет, второй разбивает ничьи первого, третий — ничьи второго. Поменять их местами — значит получить принципиально другой результат.

Пример сортировки и OFFSET без готовой базы

Этот запрос можно выполнить целиком в SQLite или PostgreSQL. Блок WITH задаёт три товара прямо внутри запроса, поэтому создавать таблицу не нужно:

WITH products(product_id, title, price) AS (
    VALUES (1, 'Клавиатура', 3000),
           (2, 'Мышь', 1500),
           (3, 'Коврик', 1500)
)
SELECT product_id, title, price
FROM products
ORDER BY price DESC, product_id ASC
LIMIT 1 OFFSET 1;

Результат — 2 | Мышь | 1500. Сначала идёт клавиатура за 3000, затем мышь и коврик. OFFSET 1 пропускает клавиатуру, LIMIT 1 оставляет одну строку. Второй ключ сортировки product_id определяет порядок товаров с одинаковой ценой. Замените OFFSET на 2 — получите коврик.

LIMIT и OFFSET: ограничение и пропуск строк, предсказуемость результата

ORDER BY определяет порядок — LIMIT говорит, сколько строк из этого порядка вернуть.

SELECT product_id, title, price
FROM products
ORDER BY price DESC, product_id ASC
LIMIT 3;

Запрос вернёт не более трёх строк. Если товаров как минимум три, это будут три самых дорогих товара. СУБД сначала сортирует весь результат, потом отрезает всё, кроме первых трёх строк.

Теперь о важном ограничении: LIMIT без ORDER BY — ловушка. Если порядок строк не зафиксирован, СУБД выберет любые три строки, и какие именно — зависит от внутреннего устройства хранилища, которое вы не контролируете. Два одинаковых запроса в разные моменты могут вернуть разный набор. Поэтому правило простое: если LIMIT нужен для конкретного отбора (топ-5, самый свежий, три самых дешёвых), ORDER BY обязателен.

OFFSET дополняет LIMIT: он говорит, сколько строк пропустить перед тем, как начать возвращать результат.

SELECT product_id, title, price
FROM products
ORDER BY price DESC, product_id ASC
LIMIT 3 OFFSET 3;

Здесь СУБД сортирует все строки по убыванию цены, пропускает первые три (строки 1–3) и возвращает следующие три (строки 4–6). Именно на этом строится постраничный вывод: каждая страница — это окно фиксированного размера, сдвинутое на нужное число строк. Второй критерий product_id ASC нужен для товаров с одинаковой ценой: уникальный идентификатор полностью определяет их взаимный порядок. Без такого критерия строки с равным price могут менять места и попадать на соседние страницы.

Формула смещения для произвольной страницы:

OFFSET = (номер_страницы - 1) × размер_страницы

При размере страницы 10 строк:

  • страница 1: LIMIT 10 OFFSET 0
  • страница 2: LIMIT 10 OFFSET 10
  • страница 5: LIMIT 10 OFFSET 40

OFFSET 0 — это то же самое, что не писать OFFSET вовсе, поэтому для первой страницы его обычно опускают.

Один нюанс: OFFSET не делает запрос быстрее — СУБД всё равно просматривает и отбрасывает первые m строк. При очень больших значениях OFFSET это заметно по скорости, но для начального уровня важнее понять сам механизм.

Анимация сортировки товаров по цене по убыванию и последующего ограничения результата тремя строками через LIMIT
ORDER BY выполняется до LIMIT

Фиксированный порядок предложений: SELECT … FROM … WHERE … ORDER BY … LIMIT … OFFSET

SQL — декларативный язык: вы описываете, что хотите получить, а не как именно это вычислить. Но синтаксис жёстко фиксирует, в каком порядке должны стоять предложения запроса. Нарушение порядка немедленно даёт синтаксическую ошибку — парсер просто не распознаёт конструкцию.

Правильная последовательность:

SELECT  …
FROM    …
WHERE   …
ORDER BY …
LIMIT   …
OFFSET  …

Выше показан синтаксический порядок записи предложений. Упрощённая логическая модель обработки выглядит иначе:

  1. FROM определяет источник строк.
  2. WHERE отбирает строки.
  3. SELECT формирует столбцы результата.
  4. ORDER BY упорядочивает результат.
  5. OFFSET / LIMIT пропускают начало и ограничивают число строк.

Это модель для чтения запроса, а не буквальный физический план: оптимизатор может переставлять операции, сохраняя результат. Написать ORDER BY перед WHERE нельзя из-за грамматики SQL.

Попробуйте мысленно переставить LIMIT перед ORDER BY:

-- Неверно: так написать нельзя, будет ошибка
SELECT title, price FROM products LIMIT 5 ORDER BY price;

Даже если бы синтаксис это допускал, смысл стал бы другим: LIMIT взял бы пять произвольных строк, и только потом их отсортировали. Результат — не «пять самых дешёвых», а «пять случайных, упорядоченных по цене».

В запросах к таблице используется FROM, а WHERE и ORDER BY необязательны. SQLite также допускает SELECT без FROM, когда нужно вычислить выражение или вернуть константу, например SELECT 1;. Но если оба присутствуют, WHERE всегда идёт первым. Запрос без WHERE, но с ORDER BY корректен: СУБД просто сортирует все строки таблицы.

Практический ориентир: когда пишете запрос, двигайтесь сверху вниз — сначала FROM (откуда), затем WHERE (какие строки), затем ORDER BY (в каком порядке), затем LIMIT/OFFSET (сколько и с какого места).

Задача 1

Запрос SELECT * FROM products ORDER BY price; возвращает строки в каком порядке?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 2

Аналитик выводит по 10 заказов на страницу. Порядок должен быть стабильным: сначала date ASC, а при одинаковой дате — order_id ASC. Какой запрос вернёт вторую страницу?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 3

Разработчик пишет запрос «SELECT * FROM orders LIMIT 1;» без ORDER BY, чтобы получить заказ с наименьшей суммой. Что может пойти не так?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 4

В каком порядке предложения должны располагаться в запросе с WHERE, ORDER BY и LIMIT?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 5

Три первых заказа в журнале

Верните три заказа, которые должны стоять первыми в журнале: сначала более новая order_date, при одинаковой дате — большая amount, а при полном совпадении даты и суммы — меньший order_id. Выведите order_id, order_date, amount.

РешениеSQL · SQLite

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 6

Вторая страница каталога

Каталог показывает по два товара на странице. Получите вторую страницу: отсортируйте товары по price по возрастанию, а при равной цене — по product_id по возрастанию; затем пропустите первые две строки и верните не более двух следующих. Выведите product_id, title, price.

РешениеSQL · SQLite

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задачи к уроку

Все задачи курса →

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку