В рейтинге два участника делят второе место, но следующий должен получить третье, а не четвёртое. Какую семантику ранжирования следует выбрать?
Используйте оконную функцию 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.
Минимальный пример:
Здесь ранжирование выполняется по всему набору строк, потому что в оконном определении нет разбиения на группы. Если нужно строить отдельный рейтинг внутри подразделений, добавляют разбиение по подразделению; порядок рангов тогда начинается заново в каждой такой части.
RANK сохраняет количество строк, занявших более высокие позиции, поэтому после ничьей пропускает номера. ROW_NUMBER всегда выдаёт уникальные последовательные номера и не считает участников с одинаковым показателем одной позицией; при равенстве значений порядок может зависеть от СУБД или плана выполнения, если не задать дополнительный критерий.
Следует заранее определить, что означает место в предметной области: плотный рейтинг без пропусков, спортивный рейтинг с пропусками или уникальный порядковый номер. Нельзя заменять одну семантику другой только ради визуально удобной нумерации.
В отчёте по продажам требовалось ранжировать менеджеров внутри каждого региона. Два менеджера с одинаковой выручкой должны были занимать одно место, а следующая позиция должна идти сразу после них.
Рассматривались три варианта. ROW_NUMBER давал уникальные места, но несправедливо разделял менеджеров с одинаковой выручкой. RANK корректно обрабатывал ничью, однако создавал пропуски, что не соответствовало требованиям отчёта. DENSE_RANK сохранил одинаковое место для равных результатов и последовательную нумерацию после ничьей.
Выбрали DENSE_RANK с разбиением по региону и сортировкой по выручке по убыванию. В результате рейтинг стал независимым между регионами, а места соответствовали согласованной бизнес-семантике.
ROW_NUMBER присваивает каждой строке уникальный номер, даже если значения сортировки совпадают. DENSE_RANK присваивает одинаковым значениям один ранг, поэтому несколько строк могут иметь одинаковое место. Если требуется стабильный и однозначный порядок строк, к сортировке добавляют уникальный идентификатор, но это уже не превращает ROW_NUMBER в функцию для обработки ничьих.
Рейтинг будет вычисляться отдельно внутри каждого подразделения, а нумерация начнётся с первого места в каждой части. Без разбиения сравниваются все строки результата, поэтому менеджер с лучшим показателем в одном подразделении может влиять на позиции менеджеров из другого.
Обычно оконный результат нельзя использовать в обычном фильтре того же уровня, где он вычисляется, поскольку фильтрация этого уровня логически выполняется раньше формирования оконного результата. Для фильтрации по рангу используют внешний запрос или другой отдельный уровень обработки: сначала вычисляют ранг для всех нужных строк, затем отбирают строки с требуемым значением ранга. Это также позволяет сохранить все строки, участвующие в корректном расчёте рейтинга.