Программирование SQLАгрегация и оконные функцииРазработчик аналитических SQL-запросов

В таблице несколько строк имеют одинаковое значение сортировки. Что гарантирует и чего не гарантирует ROW N...

В таблице несколько строк имеют одинаковое значение сортировки. Что гарантирует и чего не гарантирует ROW_NUMBER в такой ситуации?

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

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

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

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

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

Однако сортировка по неуникальному признаку оставляет несколько допустимых порядков. SQL не обязан выбирать один и тот же порядок между такими строками, если запрос явно не задаёт дополнительный критерий.

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

Предположим, нужно выбрать первую операцию каждого клиента по времени. Если у двух операций одинаковое время, сортировка только по времени не определяет, какая операция будет первой.

Из-за этого результат может меняться после изменения плана выполнения, статистики, индекса или объёма данных. Такой запрос особенно опасен для отчётов, загрузок данных и бизнес-логики, где выбранная строка должна быть стабильной.

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

ROW_NUMBER нумерует строки отдельно в каждом разделе PARTITION BY и следует порядку, заданному в ORDER BY окна. Если значения всех выражений сортировки совпадают, строки являются неразличимыми с точки зрения указанного порядка, поэтому их взаимная последовательность не определена.

Нужно добавить детерминирующий критерий: обычно первичный ключ, идентификатор события или другую уникальную комбинацию. Направление сортировки также существенно: при выборе самой ранней записи используется ASC, самой поздней — DESC.

WITH numbered AS ( SELECT client_id, operation_id, operation_time, ROW_NUMBER() OVER ( PARTITION BY client_id ORDER BY operation_time, operation_id ) AS rn FROM operations ) SELECT client_id, operation_id, operation_time FROM numbered WHERE rn = 1;

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

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

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

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

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

Выбранное решение использовало ROW_NUMBER с сортировкой по времени и уникальному идентификатору события. Это явно зафиксировало правило выбора, сделало результат повторяемым и позволило использовать единый запрос для отчёта и контрольной загрузки.

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

  1. Достаточно ли добавить любой столбец в ORDER BY окна?

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

  2. Гарантирует ли детерминированный ORDER BY окна порядок строк в итоговом результате?

    Нет. Он определяет порядок, используемый для вычисления ROW_NUMBER, но не обязан определять физический порядок выдачи строк. Для порядка результата нужен отдельный внешний ORDER BY.

  3. Можно ли заменить ROW_NUMBER на RANK, если нужно выбрать одну строку?

    Обычно нет. При совпадении значений сортировки RANK присвоит нескольким строкам один и тот же минимальный ранг, поэтому фильтр по первому рангу может вернуть несколько строк. ROW_NUMBER подходит, когда требуется ровно одна строка, но только при явно заданном правиле разрешения ничьей.