0Pricing
Coding Interview Prep · Урок

ROW_NUMBER для уникальной нумерации

Присваивайте детерминированный номер строки внутри каждого раздела.

«ROW_NUMBER для уникальной нумерации» — бесплатный урок Coding Interview Prep на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Coding Interview Prep, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Coding Interview Prep содержит 4 уроков всего.

Что гарантирует ROW_NUMBER

ROW_NUMBER() присваивает каждой строке внутри её раздела уникальное целое число без пропусков, начиная с 1 и следуя ORDER BY окна. Две строки никогда не получают один и тот же номер, даже если их значения сортировки совпадают.

Именно поэтому на собеседованиях эту функцию выбирают для задач «выбрать одну строку из каждой группы» и устранения дубликатов. Другие функции ранжирования (RANK, DENSE_RANK) не гарантируют уникальность при совпадениях.

Минимальный запрос с ROW_NUMBER

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

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

SELECT
  name,
  hire_date,
  ROW_NUMBER() OVER (ORDER BY hire_date DESC) AS rn
FROM employees;

Нумерация внутри разделов

Добавьте PARTITION BY, чтобы независимо нумеровать строки внутри каждой группы. Счётчик сбрасывается на 1 при каждом новом значении раздела.

В этом примере каждый отдел получает собственную последовательность 1, 2, 3, упорядоченную по зарплате. Самый высокооплачиваемый сотрудник каждого отдела получает номер 1 — это основа шаблона выборки первых N строк в каждой группе.

SELECT
  department,
  name,
  salary,
  ROW_NUMBER() OVER (
    PARTITION BY department
    ORDER BY salary DESC
  ) AS dept_rn
FROM employees;

ROW_NUMBER при совпадениях: ничьих не бывает

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

  • RANK присвоил бы одинаковым строкам один и тот же ранг.
  • ROW_NUMBER произвольно выбирает порядок между ними.

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

SELECT
  name,
  salary,
  ROW_NUMBER() OVER (
    ORDER BY salary DESC, employee_id  -- employee_id breaks ties deterministically
  ) AS rn
FROM employees;

Правило детерминированного разрешения равенств

Если Ваш ORDER BY не задаёт строгий порядок — то есть ни одна комбинация столбцов не уникальна, — нумерация строк может меняться между запусками даже для одних и тех же данных. На собеседованиях эту ловушку часто проверяют в задачах о постраничной навигации и «последней записи».

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

-- Unstable: many rows can share the same created_at
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)

-- Stable: id guarantees a single deterministic winner
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC, id DESC)

Разбор примера: последний заказ каждого клиента

Классическая задача: «Верните самый свежий заказ каждого клиента». Пронумеруйте заказы каждого клиента по дате в порядке убывания, а затем оставьте rn = 1 во внешнем запросе.

Поскольку ROW_NUMBER гарантирует ровно одну строку с номером 1 в каждом разделе, Вы получите ровно одну строку на клиента — без дубликатов, даже если два заказа имеют одну и ту же временную метку: дополнительный ключ разрешает равенство.

SELECT customer_id, order_id, order_date, amount
FROM (
  SELECT
    customer_id, order_id, order_date, amount,
    ROW_NUMBER() OVER (
      PARTITION BY customer_id
      ORDER BY order_date DESC, order_id DESC
    ) AS rn
  FROM orders
) t
WHERE rn = 1;

Разбор примера: устранение дубликатов строк

ROW_NUMBER — самый удобный инструмент для устранения дубликатов. Разбейте данные на разделы по столбцам, определяющим дубликат, задайте правило предпочтения в сортировке, а затем оставьте rn = 1.

Здесь строки с одинаковым адресом электронной почты считаются дубликатами, и сохраняется созданная раньше всех. Все строки с rn > 1 являются дубликатами, которые можно удалить или проигнорировать.

SELECT id, email, created_at
FROM (
  SELECT
    id, email, created_at,
    ROW_NUMBER() OVER (
      PARTITION BY email
      ORDER BY created_at ASC, id ASC
    ) AS rn
  FROM users
) t
WHERE rn = 1;

ROW_NUMBER для постраничной навигации

До того как OFFSET/FETCH стали универсальными, для постраничной навигации использовался ROW_NUMBER; он по-прежнему применяется в SQL Server и многих заданиях ETL. Строки нумеруют в стабильном порядке, а затем во внешнем запросе фильтруют диапазон номеров.

Важно для собеседования: постраничная навигация корректна только при детерминированном порядке сортировки — иначе одна и та же строка может оказаться на двух страницах или полностью исчезнуть из результатов.

SELECT *
FROM (
  SELECT *, ROW_NUMBER() OVER (ORDER BY created_at, id) AS rn
  FROM articles
) t
WHERE rn BETWEEN 21 AND 40;  -- page 2, 20 per page

Присваивание последовательности для изменения порядка данных

Иногда Вам нужен только последовательный индекс: например, чтобы перенумеровать строки после сортировки, создать метку от 1 до N или сопоставить строки со сгенерированным рядом. ROW_NUMBER в нужном порядке создаёт плотную последовательность без пропусков.

Так можно также присвоить произвольному неупорядоченному набору стабильные позиции для последующего соединения.

SELECT
  ROW_NUMBER() OVER (ORDER BY score DESC, player_id) AS leaderboard_position,
  player_id,
  score
FROM scores;

ROW_NUMBER и COUNT для «N-й строки»

Если Вас просят найти «третий по свежести заказ» или «вторую строку», используйте ROW_NUMBER и фильтруйте по точному значению во внешнем запросе. Поскольку номера уникальны, rn = 3 возвращает ровно одну строку.

Сравните это с RANK: если Вам нужно второе различное значение, например вторая по величине зарплата с учётом совпадений, ROW_NUMBER — неподходящий инструмент. Здесь нужен DENSE_RANK, который рассматривается в следующем уроке.

SELECT order_id, order_date
FROM (
  SELECT order_id, order_date,
         ROW_NUMBER() OVER (ORDER BY order_date DESC, order_id DESC) AS rn
  FROM orders
) t
WHERE rn = 3;  -- exactly the 3rd most recent order

Повторение: распространённые ошибки

Не путайте следующие моменты, даже под давлением собеседования:

  • ROW_NUMBER всегда даёт уникальные номера без пропусков внутри раздела.
  • Ему нужен ORDER BY в OVER; без уникального дополнительного ключа результаты при совпадениях недетерминированы.
  • Его нельзя фильтровать в WHERE — оберните вычисление в подзапрос или CTE.
  • Используйте его для получения одной строки на группу, устранения дубликатов и постраничной навигации; применяйте DENSE_RANK, когда одинаковые значения должны получать один номер.

Быстрая проверка

Как ROW_NUMBER обрабатывает строки с одинаковым значением в ORDER BY?

Повторение: детерминированная нумерация

ROW_NUMBER — Ваш инструмент для уникальной нумерации без пропусков внутри разделов. Вы научились:

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

Далее Вы увидите, как RANK и DENSE_RANK намеренно присваивают одинаковые номера совпадающим значениям и чем различается поведение их пропусков.

Часто задаваемые вопросы

Урок «ROW_NUMBER для уникальной нумерации» бесплатный?

Да — полный текст урока «ROW_NUMBER для уникальной нумерации» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Coding Interview Prep, подпишись на CoddyKit PRO. Курс Coding Interview Prep содержит 4 уроков всего.

Чему я научусь в уроке «ROW_NUMBER для уникальной нумерации»?

Присваивайте детерминированный номер строки внутри каждого раздела. Ты практикуешь Coding Interview Prep с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Coding Interview Prep?

Предыдущий опыт не требуется. Coding Interview Prep на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «ROW_NUMBER для уникальной нумерации»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Coding Interview Prep?

Да. Каждый урок Coding Interview Prep включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. OVER, PARTITION BY и ORDER BY
  2. ROW_NUMBER для уникальной нумерации
  3. RANK и DENSE_RANK при совпадениях
  4. Фильтрация по результату оконной функции
← Назад к Coding Interview Prep