SQL

БЛОК 2. Чтение данных — 8. ORDER BY + LIMIT

📚 22 вопросовПройти тест →
Лекция

БЛОК 2. Чтение данных — 8. ORDER BY + LIMIT

SQL

8. ORDER BY + LIMIT

🧭 Введение: почему без сортировки страница «прыгает»

Когда таблица становится больше, быстро появляется практический вопрос: какие записи показать первыми и сколько строк вернуть за один запрос.
Именно здесь работают ORDER BY, LIMIT и OFFSET.
Без явной сортировки страница может выглядеть «случайной».
А без понятной пагинации API и интерфейс начинают дублировать или пропускать записи.
💡 Совет: LIMIT почти всегда используйте вместе с ORDER BY, иначе порядок строк может быть непредсказуемым.
Вывод: ORDER BY + LIMIT/OFFSET — это базовый каркас чтения данных списками и страницами.

⚠️ Проблема -> решение

Частая ошибка новичка: сначала пишут SELECT ... LIMIT 20, а потом удивляются, что на следующих запросах состав строк меняется.
Вторая ошибка: сортируют только по одному полю, где много одинаковых значений, и получают «плавающие» страницы.
Решение:
  1. сначала фиксируем бизнес-порядок (ORDER BY),
  2. при необходимости добавляем второй/третий ключ сортировки,
  3. только после этого ограничиваем выдачу через LIMIT,
  4. для страниц используем LIMIT + OFFSET с понятной формулой.
🟢 Если совсем просто: Сначала порядок, потом размер страницы.
🎯 Как понять, что этап прошёл успешно: Вы можете объяснить, почему конкретная строка попала в страницу №2, а не в №1.

🛠️ Чем помогает и как работает

Эта тема нужна не только для «красивого списка».
Она напрямую влияет на UX, API-контракты и предсказуемость аналитических отчётов.
🟢 Если совсем просто: Вы управляете тем, какие строки идут первыми и сколько строк отдавать за раз.
🎯 Как понять, что этап прошёл успешно: Ваш запрос стабильно возвращает тот же порядок при одинаковых данных.
Чем помогает:
  • делает выдачу предсказуемой;
  • позволяет строить страницы (page 1, page 2, ...);
  • уменьшает количество «прыгающих» записей в интерфейсе;
  • упрощает ревью SQL и контракт API.
Как это работает:
  • Шаг 1: формулируем порядок строк в терминах бизнеса.
  • Шаг 2: переводим порядок в ORDER BY col1 [ASC|DESC], col2 ....
  • Шаг 3: ограничиваем размер ответа через LIMIT.
  • Шаг 4: для следующей страницы считаем OFFSET.
  • Шаг 5: проверяем стабильность порядка (tie-breaker, чаще id).
Вывод: Стабильная выдача = корректный ORDER BY + аккуратный LIMIT/OFFSET.

📚 Ключевые термины (простыми словами)

Перед практикой важно зафиксировать словарь.
🟢 Если совсем просто: Эти термины описывают, как SQL режет список на страницы.
🎯 Как понять, что этап прошёл успешно: Вы можете объяснить разницу между LIMIT и OFFSET на одном примере.
  • ORDER BY (сортировка) — задаёт порядок строк в результате.
  • ASC (ascending) — сортировка по возрастанию.
  • DESC (descending) — сортировка по убыванию.
  • LIMIT (ограничение количества) — сколько строк вернуть.
  • OFFSET (смещение) — сколько строк пропустить перед возвратом.
  • Pagination (пагинация) — разбиение большого списка на страницы.
  • Tie-breaker (разрешение равенства) — дополнительная колонка сортировки при одинаковых значениях основного поля.
  • Stable order (устойчивый порядок) — детерминированный порядок строк между запросами.
Вывод: Без tie-breaker сортировка часто формально корректна, но практически нестабильна.

🔗 1. Сортировка по нескольким колонкам

ORDER BY поддерживает сразу несколько полей.
Это главный способ сделать порядок устойчивым и понятным.
🟢 Если совсем просто: Первая колонка — основной порядок, вторая — уточняющий порядок, если в первой равенство.
🎯 Как понять, что этап прошёл успешно: Записи с одинаковым значением первого поля идут в чётком и повторяемом порядке.
Назначение: Сделать сортировку детерминированной при равных значениях.
Простыми словами: Если у двух строк одинаковая дата, вы решаете, кто из них будет выше через второе поле.
Для новичка: Частый устойчивый шаблон: ORDER BY created_at DESC, id DESC.
Аналогия: Сначала сортируете людей по фамилии, а внутри одинаковых фамилий — по имени.
Пример:
SELECT  id,  customer_id,  created_at,  total_amountFROM ordersORDER BY created_at DESC, id DESC;
🔎 Как это происходит на практике:
  • Контекст: лента заказов в админке.
  • Действия: основной порядок задают по времени, tie-breaker — по id.
  • Результат: страница не «прыгает» между перезагрузками.
Характеристики:
  • можно сортировать по 1, 2 и более колонкам;
  • направление (ASC/DESC) задаётся отдельно для каждой колонки;
  • в реальных списках tie-breaker почти всегда обязателен.
Когда использовать: Всегда, когда есть риск равных значений в основном поле сортировки.
Вывод: Многоколоночный ORDER BY — базовый способ стабилизировать список.

🔼 2. ASC / DESC и смешанная сортировка

Направление сортировки влияет на бизнес-смысл выдачи.
Чаще всего новые записи показывают сверху (DESC), а «лёгкие» справочники — по алфавиту (ASC).
🟢 Если совсем просто: ASC — от меньшего к большему, DESC — от большего к меньшему.
🎯 Как понять, что этап прошёл успешно: Вы осознанно выбираете направление для каждой колонки, а не оставляете «по умолчанию».
Назначение: Контролировать «верх» и «низ» списка под задачу.
Простыми словами: Вы явно задаёте, что считать «приоритетом наверху».
Для новичка: Не полагайтесь на умолчания, пишите ASC/DESC явно.
Аналогия: Сортировка товаров: сначала дорогие (price DESC), а внутри них — по названию (name ASC).
Пример:
SELECT  id,  priority,  created_at,  titleFROM ticketsORDER BY priority DESC, created_at DESC, title ASC;
Нюанс с NULL в сортировке (PostgreSQL):
SELECT  id,  shipped_atFROM ordersORDER BY shipped_at DESC NULLS LAST, id DESC;
🔎 Как это происходит на практике:
  • Контекст: support-очередь.
  • Действия: наверх поднимают высокий приоритет и более свежие обращения.
  • Результат: оператор сначала видит действительно важные тикеты.
Характеристики:
  • ASC — направление по умолчанию, но лучше указывать явно;
  • у каждой колонки может быть своё направление;
  • для nullable-полей полезно явно задавать NULLS FIRST / NULLS LAST;
  • читаемость запроса выше, когда порядок отражает бизнес-логику.
Когда использовать: Когда нужно комбинировать «важность», «свежесть» и «читаемость».
Вывод: Явные ASC/DESC убирают двусмысленность и ускоряют ревью.

✂️ 3. LIMIT / OFFSET

LIMIT и OFFSET ограничивают окно выдачи.
Это техническая база любой «постраничной» выдачи в SQL.
🟢 Если совсем просто: LIMIT — размер страницы, OFFSET — сдвиг к нужной странице.
🎯 Как понять, что этап прошёл успешно: Вы легко считаете OFFSET по формуле и не путаете страницы.
Назначение: Получать только нужный кусок большого набора данных.
Простыми словами: SQL как бы говорит: «пропусти N строк и дай следующие M».
Для новичка: OFFSET = (page - 1) * page_size.
Аналогия: Это как листать каталог: сначала первые 20 товаров, затем следующие 20.
Пример:
SELECT  id,  email,  created_atFROM usersORDER BY created_at DESC, id DESCLIMIT 20OFFSET 40;
🔎 Как это происходит на практике:
  • Контекст: API /users?page=3&pageSize=20.
  • Действия: backend считает offset = 40.
  • Результат: клиент получает корректную третью страницу.
Характеристики:
  • LIMIT ограничивает число возвращаемых строк;
  • OFFSET пропускает заданное число строк до LIMIT;
  • чем больше OFFSET, тем больше строк БД всё равно должна пройти до нужного окна;
  • без ORDER BY пагинация становится ненадёжной.
Когда использовать: Для базовой пагинации в интерфейсах и API.
Вывод: LIMIT/OFFSET работает корректно только вместе с устойчивой сортировкой.

📄 4. Базовая пагинация

Пагинация — это не отдельная магия, а комбинация ORDER BY + LIMIT + OFFSET.
Главная цель — получать предсказуемые страницы с фиксированным размером.
🟢 Если совсем просто: Стабильный порядок + формула offset = правильные страницы.
🎯 Как понять, что этап прошёл успешно: Переход между страницами не даёт дублей и «потерянных» строк на статичных данных.
Назначение: Стандартизировать чтение больших списков по страницам.
Простыми словами: Вы выбираете «линию разреза» большого списка на равные части.
Для новичка: Сначала задайте порядок, только потом считайте страницы.
Аналогия: Книга с оглавлением: сначала порядок глав, потом номер страницы.
Пример:
SELECT  id,  name,  price,  updated_atFROM productsORDER BY updated_at DESC, id DESCLIMIT 12OFFSET 24;
🔎 Как это происходит на практике:
  • Контекст: витрина интернет-магазина.
  • Действия: frontend передаёт page и pageSize, backend считает OFFSET.
  • Результат: пользователь видит ожидаемые карточки на каждой странице.
Характеристики:
  • формула OFFSET должна быть единой в проекте;
  • tie-breaker снижает риск «прыгающих» записей;
  • большие OFFSET могут замедлять запрос (для старших тем есть keyset pagination).
Когда использовать: Когда нужна простая и понятная постраничная навигация.
Вывод: Базовая пагинация проста, если порядок строк задан явно и стабильно.

🧨 5. Типичные ловушки ORDER BY + LIMIT

Большинство проблем здесь не синтаксические, а логические.
Запрос выполняется, но результат неудобен или нестабилен.
🟢 Если совсем просто: Главные ловушки: LIMIT без сортировки и сортировка без tie-breaker.
🎯 Как понять, что этап прошёл успешно: Вы заранее проверяете устойчивость порядка перед запуском пагинации.
Назначение: Показать ошибки, из-за которых список «ломается» в интерфейсе.
Простыми словами: Формально правильный SQL может давать плохой UX.
Для новичка: Перед LIMIT всегда задайте ORDER BY и подумайте, что делать при равенстве.
Аналогия: Это как очередь без номера талона: кто первый, непонятно.
Пример:
SELECT  id,  created_atFROM ordersORDER BY created_at DESCLIMIT 20;
Устойчивый вариант:
SELECT  id,  created_atFROM ordersORDER BY created_at DESC, id DESCLIMIT 20;
🔎 Как это происходит на практике:
  • Контекст: лента новых заказов, где несколько заказов могут иметь одинаковый created_at.
  • Действия: добавляют вторую колонку сортировки.
  • Результат: порядок становится повторяемым между запросами.
Характеристики:
  • LIMIT без ORDER BY не даёт бизнес-гарантий порядка;
  • один ключ сортировки часто недостаточен;
  • OFFSET-пагинация стабильна на статичных данных, но при вставках/удалениях между запросами возможны дубли и пропуски;
  • для страниц с OFFSET устойчивый порядок обязателен.
Мини-мостик к keyset pagination:
SELECT  id,  created_atFROM ordersWHERE (created_at, id) < (:last_created_at, :last_id)ORDER BY created_at DESC, id DESCLIMIT 20;
Когда использовать: Как чек-лист перед релизом API с пагинацией.
Вывод: Пагинация над нестабильным порядком почти всегда приводит к багам.

⚖️ Сравнение подходов

ПодходЧто решаетОграничение
ORDER BY ... LIMIT Nвзять top-N строкне даёт «следующую страницу»
ORDER BY ... LIMIT N OFFSET Mбазовая пагинацияпри больших OFFSET может тормозить
keyset/seek (WHERE tuple < last_seen)стабильнее при активных вставкахнужен курсор последней строки
ORDER BY с tie-breakerустойчивый порядоктребует осознанного выбора второй колонки
Вывод: Для базовой пагинации минимально нужен ORDER BY + LIMIT + OFFSET, а tie-breaker делает результат стабильным.

📌 Must-know факты

  • LIMIT без ORDER BY не гарантирует осмысленный порядок.
  • В ORDER BY можно и нужно использовать несколько колонок.
  • Формула базовой пагинации: OFFSET = (page - 1) * page_size.
  • Направление сортировки задаётся для каждой колонки отдельно.
  • Стабильный порядок для страниц обычно требует tie-breaker (id).
  • При больших OFFSET производительность может снижаться.
  • Если данные активно меняются между запросами, OFFSET может давать дубли/пропуски.

🧨 Частые мифы

Миф: LIMIT 20 всегда возвращает «20 самых новых» записей.
Как правильно: Без ORDER BY LIMIT просто режет произвольный текущий порядок.
📎 Почему это важно: Иначе пользователь видит непредсказуемую выдачу и дубли на страницах.
Миф: Если есть ORDER BY created_at, этого всегда достаточно.
Как правильно: При равных датах добавляйте tie-breaker, например id.
📎 Почему это важно: Без второго ключа страницы могут «скакать» между запросами.
Миф: ASC/DESC можно не писать, SQL «и так поймёт».
Как правильно: Явно задавайте направление, особенно в многошаговой сортировке.
📎 Почему это важно: Явность снижает риск ошибок и ускоряет code review.
Миф: OFFSET — это номер страницы.
Как правильно: OFFSET — количество пропускаемых строк, его нужно вычислять по формуле.
📎 Почему это важно: Ошибки в формуле сразу ломают пагинацию API.

❓ Часто спрашивают на собеседованиях

Вопрос: Зачем использовать несколько колонок в ORDER BY? ✅ Ответ: Чтобы сделать порядок детерминированным, особенно когда значения первой колонки совпадают.
Вопрос: Что будет, если использовать LIMIT без ORDER BY? ✅ Ответ: Вернётся ограниченное число строк в недетерминированном порядке, который нельзя считать бизнес-стабильным.
Вопрос: Как считать OFFSET для страницы page? ✅ Ответ: OFFSET = (page - 1) * page_size.
Вопрос: Можно ли комбинировать ASC и DESC в одном запросе? ✅ Ответ: Да, направление задаётся отдельно для каждой колонки в ORDER BY.
Вопрос: Почему для пагинации часто добавляют id в сортировку? ✅ Ответ: id работает как tie-breaker и стабилизирует порядок между одинаковыми значениями основного поля.
Вопрос: В чём базовый риск больших OFFSET? ✅ Ответ: Запросу приходится пропускать всё больше строк, что может ухудшать производительность.

🚫 Типичные ошибки

Ошибка 1: использовать LIMIT без ORDER BY

Неправильно: SELECT * FROM orders LIMIT 20
Правильно: SELECT * FROM orders ORDER BY created_at DESC, id DESC LIMIT 20
Почему: Без сортировки вы не контролируете, какие 20 строк попадут в ответ.

Ошибка 2: сортировать только по неуникальному полю

Неправильно: ORDER BY created_at DESC
Правильно: ORDER BY created_at DESC, id DESC
Почему: Равные значения первого поля без tie-breaker дают нестабильный порядок.

Ошибка 3: путать формулу OFFSET

Неправильно: OFFSET = page * page_size
Правильно: OFFSET = (page - 1) * page_size
Почему: Неверная формула сдвигает страницы и пропускает записи.

Ошибка 4: не указывать направления сортировки явно

Неправильно: Оставлять смешанную сортировку частично без ASC/DESC.
Правильно: Явно фиксировать направление для всех ключевых колонок.
Почему: Так логика запроса читается однозначно.

Ошибка 5: не проверять страницы на дубль/пропуск

Неправильно: Тестировать только первую страницу.
Правильно: Проверять минимум 2-3 соседние страницы на статичных данных.
Почему: Проблемы стабильности чаще всего проявляются между страницами.

✅ Best Practices

  • Перед LIMIT всегда фиксируйте явный ORDER BY.
  • Для реальных списков добавляйте tie-breaker (id) в конец сортировки.
  • Пишите ASC/DESC явно, даже если направление кажется очевидным.
  • Формулу OFFSET держите единой в backend и frontend.
  • Проверяйте пагинацию не только на page 1, но и на соседних страницах.
  • Для частого top-N (последние 20) обычно делают индекс под сортировку, например (created_at, id).
  • Для больших объёмов данных заранее оценивайте влияние больших OFFSET.

🧾 Заключение

ORDER BY + LIMIT — это базовый инструмент чтения данных «как в продукте», а не «как в таблице».
Когда вы добавляете OFFSET и устойчивую сортировку, получаете предсказуемую пагинацию для API и UI.

Ключевые мысли

  • Сначала порядок, потом ограничение строк.
  • Многоколоночная сортировка делает выдачу устойчивой.
  • LIMIT/OFFSET — простой и рабочий базовый способ пагинации.
  • Больше всего багов здесь логические, а не синтаксические.
🎯

Проверьте знания

Закрепите материал — пройдите тест по теме «БЛОК 2. Чтение данных — 8. ORDER BY + LIMIT»

Пройти тест →