Сортировка в SQL: что важно понять сразу
ORDER BY не делает результат стабильным сам по себе. Разбираем ASC и DESC, NULL, одинаковые значения, сортировку по выражениям, LIMIT и пагинацию на примерах PostgreSQL 16.

За 10 минут вы соберёте запрос, который возвращает строки в предсказуемом порядке, не теряет записи при одинаковых значениях и не ломает пагинацию. Примеры ниже написаны для PostgreSQL 16; базовый синтаксис SQL переносим, но правила NULL и некоторые функции зависят от СУБД.
Что нужно иметь до начала
Достаточно PostgreSQL 16 и любой среды, где можно выполнить SQL: psql, DBeaver, DataGrip или встроенный редактор облачной базы. В примерах используется таблица orders с полями id, created_at, status и total_amount.
Если такой таблицы нет, сначала создайте небольшой набор данных:
WITH orders(id, created_at, status, total_amount) AS (
VALUES
(1, TIMESTAMP '2025-01-10 12:00:00', 'paid', 1200),
(2, TIMESTAMP '2025-01-10 12:00:00', 'paid', 800),
(3, TIMESTAMP '2025-01-09 18:30:00', 'new', 1500),
(4, TIMESTAMP '2025-01-11 09:15:00', 'cancelled', NULL)
)
SELECT *
FROM orders;
Пять шагов к предсказуемой сортировке
- Укажите ORDER BY явно. Без него СУБД не обязана возвращать строки в порядке вставки, по идентификатору или по физическому расположению на диске. План выполнения может измениться после создания индекса, обновления статистики или перехода на другой сервер.
SELECT id, created_at, status, total_amount FROM orders ORDER BY created_at DESC;Проверка: сначала должны идти более новые даты. Если повторить запрос несколько раз и порядок строк с одинаковой датой меняется, это не ошибка сервера — у сортировки пока нет полного ключа.
- Разделяйте направление для каждого поля.
ASCсортирует по возрастанию,DESC— по убыванию. Направление относится только к полю перед ним, поэтому его пишут отдельно. - Добавьте уникальный идентификатор последним ключом. Если две строки имеют одинаковые значения всех полей из
ORDER BY, их взаимный порядок не определён. Для стабильного результата добавьте поле, которое однозначно различает строки. - Обработайте NULL.
NULLозначает отсутствие значения, а не ноль и не пустую строку. В PostgreSQL приASCзначенияNULLпо умолчанию идут последними, а приDESC— первыми. На это поведение нельзя рассчитывать при переносе запроса в другую СУБД. - Сортируйте до LIMIT.
LIMITтолько ограничивает число возвращённых строк. БезORDER BYон не означает «первые по времени» или «самые дешёвые».
SELECT id, created_at, total_amount
FROM orders
ORDER BY created_at DESC, total_amount ASC;
Проверка: даты идут от новых к старым, а заказы с одинаковой датой — от меньшей суммы к большей. Запись ORDER BY created_at, total_amount DESC дала бы другой результат: дата по возрастанию, сумма по убыванию.
SELECT id, created_at, total_amount
FROM orders
ORDER BY created_at DESC, id DESC;
Проверка: две записи с одинаковым created_at теперь всегда сравниваются по id. Обычно таким последним ключом служит первичный ключ, но он не обязан быть первым полем сортировки.
SELECT id, total_amount
FROM orders
ORDER BY total_amount DESC NULLS LAST, id DESC;
Проверка: заказ без суммы должен оказаться после заказов с числовыми значениями, даже при сортировке по убыванию. Явное NULLS LAST фиксирует намерение в самом запросе.
SELECT id, created_at, total_amount
FROM orders
ORDER BY created_at DESC, id DESC
LIMIT 20;
Проверка: запрос возвращает не более 20 строк, а при повторном запуске сохраняет тот же порядок, пока данные не изменились. Для настоящей пагинации одного LIMIT недостаточно.
Три конструкции, которые часто путают
Сортировка по номеру столбца
В PostgreSQL можно написать ORDER BY 2, чтобы сортировать по второму выражению в SELECT. Это коротко, но хрупко: после перестановки столбцов запрос начинает сортировать по-другому без явной ошибки.
SELECT id, created_at, total_amount
FROM orders
ORDER BY created_at DESC, id DESC;
Для рабочих запросов лучше указывать имя столбца или выражение. Так ревьюеру не приходится считать позиции, а изменение списка полей не меняет смысл сортировки.
Сортировка по вычисляемому значению
Поле в ORDER BY не обязано присутствовать в выдаче. Например, сначала можно показать дорогие оплаченные заказы, а затем остальные:
SELECT id, status, total_amount
FROM orders
ORDER BY
CASE WHEN status = 'paid' THEN 0 ELSE 1 END,
total_amount DESC NULLS LAST,
id DESC;
Сначала выполняется сравнение по результату CASE, затем по сумме, затем по идентификатору. Если такая сортировка используется часто, проверьте план и подумайте об отдельном поле или индексе: выражение может потребовать вычисления для большого числа строк.
Сортировка текста и регистра
Порядок строк зависит от типа данных, локали и правил сортировки базы. Сравнение Apple и apple не стоит считать одинаковым во всех СУБД. Если бизнес-правило требует игнорировать регистр, явно задайте выражение, например ORDER BY lower(name), и проверьте, как это влияет на индекс.
Стабильная сортировка — это не «получилось красиво в консоли». Это полный набор правил, по которым для любых двух строк можно определить их место.
[1] Детерминированный порядок — порядок, который однозначно задаётся значениями ключей сортировки. Если у двух строк все ключи равны, порядок между ними не детерминирован, даже если сейчас он выглядит постоянным.
Пагинация: LIMIT и OFFSET против курсора
Связка LIMIT 20 OFFSET 40 проста для первой версии интерфейса, но на больших смещениях СУБД часто приходится найти и пропустить предыдущие строки. Есть и логическая проблема: если между запросами кто-то добавил новую запись, границы страниц сдвинутся, и пользователь может увидеть дубликат или пропустить строку.
Для последовательного просмотра используйте последний увиденный ключ. При порядке «новые записи первыми» условие для следующей страницы выглядит так:
SELECT id, created_at, total_amount
FROM orders
WHERE (created_at, id) < (TIMESTAMP '2025-01-10 12:00:00', 2)
ORDER BY created_at DESC, id DESC
LIMIT 20;
Пара created_at, id должна совпадать в WHERE и ORDER BY. Если в таблице возможны одинаковые даты и идентификатор не добавлен, часть строк может потеряться между страницами.
Если сортировка «не работает»
- «column ... does not exist». Проверьте имя поля и псевдоним. В PostgreSQL псевдоним из
SELECTможно использовать вORDER BY, но не всегда вWHEREтого же уровня запроса. - Порядок меняется от запуска к запуску. Добавьте уникальный последний ключ, обычно
id. ОдногоORDER BY created_atнедостаточно, если даты повторяются. - NULL оказался в начале. Задайте
NULLS LASTилиNULLS FIRSTявно. Не заменяйте пропуск магическим числом без проверки смысла данных. - Запрос стал медленным после ORDER BY. Посмотрите план через
EXPLAIN. СУБД может сортировать большой промежуточный набор, особенно если сортировка идёт по выражению или результату соединения. - Страницы содержат дубли. Проверьте, что сортировка уникальна и условие следующей страницы использует тот же порядок. При изменениях данных между запросами OFFSET-пагинация остаётся уязвимой к сдвигам.
Что делать дальше
Возьмите один реальный запрос с LIMIT и проверьте четыре вещи: есть ли явный ORDER BY, обработан ли NULL, добавлен ли уникальный последний ключ и совпадает ли порядок с условием пагинации. Затем выполните EXPLAIN на рабочем объёме данных и сравните время, число обработанных строк и выбранный план до и после изменения.
Инвалидация кеша и сортировка результатов после кеширования — отдельная тема: сначала зафиксируйте порядок на уровне SQL, а уже затем оценивайте кеш.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: PostgreSQL 16 Documentation: Sorting Rows (ORDER BY), PostgreSQL 16 Documentation: LIMIT and OFFSET
Частые вопросы
Можно ли полагаться на порядок строк без ORDER BY?
Почему две строки с одинаковой датой меняются местами?
Чем отличаются NULL, 0 и пустая строка в сортировке?
Почему LIMIT 20 возвращает не те «первые» записи?
Что делать, если у меня не PostgreSQL 16?
Почему запрос с сортировкой стал медленным в проде?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.