Отношение — это математическое множество кортежей. Порядок элементов в множестве не определен.
ORDER BY.Сортировка выполняется слева направо:
SELECT title, category, rating, price
FROM products
ORDER BY category ASC,
rating DESC,
price ASC;
Сравнение переходит к следующему полю только при равенстве значений в предыдущем.
Если несколько строк идентичны по всем полям ORDER BY, их взаимное расположение случайно.
Золотое правило надежности: Всегда завершайте список сортировки первичным ключом (Tie-breaker): ORDER BY category ASC, rating DESC, id ASC.
NULL считается «наибольшим» значением.
ORDER BY col ASC → NULL в конце.
ORDER BY col DESC → NULL в начале!
NULL считается «наименьшим» значением.Спецификация стандарта SQL-99 устраняет двусмысленность:
-- Товары без рейтинга отправляем в конец:
ORDER BY rating DESC NULLS LAST;
-- Срочные авиарейсы без гейта ставим наверх:
ORDER BY departure_time ASC, gate ASC NULLS FIRST;
Гарантирует идентичное поведение во всех промышленных СУБД.
Сортирует строки по байтовым кодам UTF-8/ASCII:
Учитывают национальные правила алфавита:
SELECT last_name, first_name
FROM students
ORDER BY last_name COLLATE "ru_RU" ASC;
Буква «Ё» корректно упорядочивается рядом с «Е», регистронезависимое первичное сравнение.
Параметр work_mem (PostgreSQL) определяет объем памяти для одной операции сортировки или хеширования.
work_mem = 64MB может потребоваться до 6.4 ГБ RAM.ORDER BY ... LIMIT k память выделяется только под кучу из $ элементов. Сложность: (N \log k)$ вместо (N \log N)$.СУБД переключается на внешнюю сортировку слиянием:
План запроса показывает:
Sort Method: external merge Disk: 28416kB
Задержка (latency) запроса подскакивает с миллисекунд до секунд из-за дискового ввода-вывода (I/O Bottleneck).
-> Sort (cost=145.20..148.50 rows=1320)
Sort Key: rating DESC, price ASC
Sort Method: quicksort Memory: 94kB
Все данные отсортированы в оперативной памяти за микросекунды.
-> Sort (cost=85420..88120 rows=540000)
Sort Key: created_at DESC
Sort Method: external merge Disk: 42100kB
Buffers: shared hit=4210 read=18400,
temp read=5260 written=5260
Десятки мегабайт временных файлов перегружают дисковую подсистему сервера!
SELECT * FROM ledger
ORDER BY id
LIMIT 20 OFFSET 500000;
СУБД вынуждена:
Сложность (N)$ — запрос падает по тайм-ауту на далеких страницах!
Если во время чтения страницы 1 добавлена новая запись, на странице 2 пользователь увидит дубликат строки, уехавшей со страницы 1.
Вместо смещения запоминаем кортеж последней увиденной записи:
SELECT id, reg_date, amount
FROM contracts
WHERE (reg_date, id) < (, )
ORDER BY reg_date DESC, id DESC
LIMIT 20;
Поиск стартовой точки по составному B-Tree индексу выполняется за (\log N)$.
Вызовы функций к колонкам ломают использование индексов:
-- ПЛОХО: полный Seq Scan!
WHERE EXTRACT(YEAR FROM doc_date) = 2026;
WHERE lower(username) = 'ivan';
WHERE amount + 100 > 5000;
СУБД обязана вычислить функцию для каждой из миллионов строк таблицы.
Оставляйте колонку чистой для прямого спуска по B-Tree:
-- ОТЛИЧНО: быстрый Index Range Scan!
WHERE doc_date >= '2026-01-01'
AND doc_date < '2027-01-01';
WHERE amount > 4900;
SELECT * FROM tickets
WHERE status = 'OPEN'
AND department_id = 5
AND priority > 3
ORDER BY created_at DESC
LIMIT 10;
status, department_id (точные равенства первыми).created_at DESC (устраняет отдельный узел Sort в плане).priority (диапазонная фильтрация).Результат: мгновенный Index Scan без выделения памяти на сортировку!
WHERE last_name LIKE 'Иван%';
Превращается в диапазон: last_name >= 'Иван' AND last_name < 'Ивао'. Полностью поддерживается B-Tree индексом!
WHERE last_name LIKE '%иванов%';
Символ % в начале отключает B-Tree. Требуется либо Seq Scan, либо специализированный триграммный индекс (GIN/GiST с расширением pg_trgm).
ORDER BY, если порядок критичен для бизнес-логики.NULLS FIRST / NULLS LAST при сортировке по убыванию.OFFSET на Keyset-пагинацию в нагруженных интерфейсах.Collation для кириллических справочников.OFFSET 100000.WHERE.external merge Disk в EXPLAIN.OFFSET.