← Все вопросы

Как работает функция ROW_NUMBER() в SQL и зачем нужны OVER и PARTITION BY?

Задан 21 месяц назад1.1к просмотров2 ответа
9

Увидел в чужом запросе конструкцию ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) и не до конца понял, как она работает. Что делает ROW_NUMBER, зачем нужны OVER и PARTITION BY? Можно пример, например, как выбрать самую свежую запись по каждому пользователю.

2 ответа

16
✓ Принятый ответ — помог автору

ROW_NUMBER() — это оконная функция, которая присваивает каждой строке результата уникальный порядковый номер 1, 2, 3, ... в рамках заданного окна и порядка сортировки.

Разберём синтаксис OVER (PARTITION BY ... ORDER BY ...):

  • OVER — обязательная часть, которая объявляет, что функция оконная, и задаёт «окно» (набор строк), над которым она работает.
  • PARTITION BY — разбивает строки на группы (партиции). Нумерация начинается заново с 1 в каждой группе. Если PARTITION BY не указан — всё считается одной большой группой.
  • ORDER BY (внутри OVER) — задаёт порядок, в котором назначаются номера. Для ROW_NUMBER() он обязателен по смыслу: без него порядок номеров недетерминирован.

Простой пример — сквозная нумерация сотрудников по зарплате:

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

С PARTITION BY нумерация рестартует внутри каждого отдела:

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

Теперь у каждого отдела свой rn = 1 у самого высокооплачиваемого.

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

WITH ranked AS (
    SELECT
        o.*,
        ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
    FROM orders o
)
SELECT *
FROM ranked
WHERE rn = 1;

Важный момент: оконные функции нельзя использовать прямо в WHERE той же выборки (они вычисляются позже фильтрации), поэтому оборачиваем в CTE или подзапрос, а уже снаружи фильтруем WHERE rn = 1.

6

Главное, что нужно усвоить про ROW_NUMBER(): в отличие от GROUP BY, оконная функция не схлопывает строки. Все исходные строки остаются на месте, к ним просто добавляется столбец с номером. Это и делает приём «пронумеровать и взять первую» таким удобным для дедупликации и выбора топ-N по группе.

Ещё пара практических заметок:

  • Если в ORDER BY внутри окна есть ничьи (одинаковые значения), ROW_NUMBER() всё равно даст разные номера — порядок между равными строками формально не определён. Если нужна стабильность, добавьте уникальный тай-брейкер, например ORDER BY created_at DESC, id DESC.
  • ROW_NUMBER() отлично подходит для пагинации в связке с OFFSET/FETCH или фильтром по диапазону номеров.

Ваш ответ

Войдите, чтобы ответить на вопрос.
Поддержать проект