В таблице несколько строк имеют одинаковое значение сортировки. Что гарантирует и чего не гарантирует ROW_NUMBER в такой ситуации?
ROW_NUMBER гарантирует уникальный последовательный номер каждой строки внутри раздела, но не гарантирует, какая именно строка получит меньший номер среди строк с одинаковыми значениями сортировки. Чтобы результат был воспроизводимым, в оконный порядок нужно добавить столбец или комбинацию столбцов, однозначно определяющих порядок.
Оконные функции появились как способ выполнять аналитические вычисления над связанными строками без схлопывания результата в одну строку на группу. Нумерация строк позволяет решать задачи выбора первой записи, построения рейтингов и постраничной обработки без сложных самосоединений.
Однако сортировка по неуникальному признаку оставляет несколько допустимых порядков. SQL не обязан выбирать один и тот же порядок между такими строками, если запрос явно не задаёт дополнительный критерий.
Предположим, нужно выбрать первую операцию каждого клиента по времени. Если у двух операций одинаковое время, сортировка только по времени не определяет, какая операция будет первой.
Из-за этого результат может меняться после изменения плана выполнения, статистики, индекса или объёма данных. Такой запрос особенно опасен для отчётов, загрузок данных и бизнес-логики, где выбранная строка должна быть стабильной.
ROW_NUMBER нумерует строки отдельно в каждом разделе PARTITION BY и следует порядку, заданному в ORDER BY окна. Если значения всех выражений сортировки совпадают, строки являются неразличимыми с точки зрения указанного порядка, поэтому их взаимная последовательность не определена.
Нужно добавить детерминирующий критерий: обычно первичный ключ, идентификатор события или другую уникальную комбинацию. Направление сортировки также существенно: при выборе самой ранней записи используется ASC, самой поздней — DESC.
Здесь operation_id разрешает неоднозначность одинаковых временных меток. Если он уникален, первая строка для каждого клиента выбирается воспроизводимо.
Не следует путать это поведение с RANK и DENSE_RANK: они присваивают одинаковый ранг строкам с одинаковыми значениями сортировки, тогда как ROW_NUMBER всегда выдаёт разные номера. Добавление уникального критерия к сортировке может изменить порядок строк, но не устранит саму разницу в семантике этих функций.
В системе аудита требовалось определить первый статус каждого обращения. Время фиксировалось с точностью до секунды, поэтому несколько событий одного обращения могли иметь одинаковую временную метку.
Вариант с сортировкой только по времени был коротким, но нестабильным: при повторном запуске иногда выбирался другой статус. Самосоединение таблицы усложняло запрос и всё равно требовало правила разрешения одинакового времени.
Выбранное решение использовало ROW_NUMBER с сортировкой по времени и уникальному идентификатору события. Это явно зафиксировало правило выбора, сделало результат повторяемым и позволило использовать единый запрос для отчёта и контрольной загрузки.
Достаточно ли добавить любой столбец в ORDER BY окна?
Нет. Дополнительный столбец должен обеспечивать однозначный порядок либо входить в комбинацию, которая однозначно различает строки. Если после его добавления остаются совпадения, недетерминированность сохраняется.
Гарантирует ли детерминированный ORDER BY окна порядок строк в итоговом результате?
Нет. Он определяет порядок, используемый для вычисления ROW_NUMBER, но не обязан определять физический порядок выдачи строк. Для порядка результата нужен отдельный внешний ORDER BY.
Можно ли заменить ROW_NUMBER на RANK, если нужно выбрать одну строку?
Обычно нет. При совпадении значений сортировки RANK присвоит нескольким строкам один и тот же минимальный ранг, поэтому фильтр по первому рангу может вернуть несколько строк. ROW_NUMBER подходит, когда требуется ровно одна строка, но только при явно заданном правиле разрешения ничьей.