В рейтинге два участника делят второе место, но следующий должен получить третье, а не четвёртое. Какую сем...

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

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Используйте оконную функцию DENSE_RANK: одинаковые значения получат одинаковое место, а после ничьей номера продолжатся без пропусков. В отличие от RANK, она не оставит пропущенное место после группы равных результатов.

Исторический контекст

Оконные функции появились как средство аналитической обработки строк без сворачивания результата в одну строку на группу. Ранжирование решает задачу построения порядковых мест прямо в результирующем наборе, сохраняя исходные записи для дальнейшего анализа.

Постановка проблемы

В отчёте участников нужно упорядочить по показателю, причём одинаковые результаты должны иметь одинаковое место. Если выбрать неподходящую функцию, после ничьей могут появиться пропуски в нумерации, и такой рейтинг будет противоречить бизнес-правилу.

Например, при результатах 100, 90, 90, 80 функция RANK даст места 1, 2, 2, 4. Если ожидается последовательность 1, 2, 2, 3, нужна другая семантика ранжирования.

Подробное решение

DENSE_RANK присваивает одинаковым значениям одинаковый ранг и увеличивает его только при переходе к следующему отличающемуся значению. Поэтому последовательность 100, 90, 90, 80 превращается в 1, 2, 2, 3.

Минимальный пример:

SELECT employee_id, score, DENSE_RANK() OVER (ORDER BY score DESC) AS place FROM results;

Здесь ранжирование выполняется по всему набору строк, потому что в оконном определении нет разбиения на группы. Если нужно строить отдельный рейтинг внутри подразделений, добавляют разбиение по подразделению; порядок рангов тогда начинается заново в каждой такой части.

RANK сохраняет количество строк, занявших более высокие позиции, поэтому после ничьей пропускает номера. ROW_NUMBER всегда выдаёт уникальные последовательные номера и не считает участников с одинаковым показателем одной позицией; при равенстве значений порядок может зависеть от СУБД или плана выполнения, если не задать дополнительный критерий.

Следует заранее определить, что означает место в предметной области: плотный рейтинг без пропусков, спортивный рейтинг с пропусками или уникальный порядковый номер. Нельзя заменять одну семантику другой только ради визуально удобной нумерации.

Ситуация из практики

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

Рассматривались три варианта. ROW_NUMBER давал уникальные места, но несправедливо разделял менеджеров с одинаковой выручкой. RANK корректно обрабатывал ничью, однако создавал пропуски, что не соответствовало требованиям отчёта. DENSE_RANK сохранил одинаковое место для равных результатов и последовательную нумерацию после ничьей.

Выбрали DENSE_RANK с разбиением по региону и сортировкой по выручке по убыванию. В результате рейтинг стал независимым между регионами, а места соответствовали согласованной бизнес-семантике.

Что кандидаты часто упускают

  1. Чем DENSE_RANK отличается от ROW_NUMBER при совпадающих значениях?

ROW_NUMBER присваивает каждой строке уникальный номер, даже если значения сортировки совпадают. DENSE_RANK присваивает одинаковым значениям один ранг, поэтому несколько строк могут иметь одинаковое место. Если требуется стабильный и однозначный порядок строк, к сортировке добавляют уникальный идентификатор, но это уже не превращает ROW_NUMBER в функцию для обработки ничьих.

  1. Что изменится, если добавить разбиение по подразделению?

Рейтинг будет вычисляться отдельно внутри каждого подразделения, а нумерация начнётся с первого места в каждой части. Без разбиения сравниваются все строки результата, поэтому менеджер с лучшим показателем в одном подразделении может влиять на позиции менеджеров из другого.

  1. Можно ли отфильтровать строки по рангу непосредственно на том же уровне запроса?

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