DISTINCT ON — одна строка на группу и дедупликация в PostgreSQL

DISTINCT ON (expr) — расширение PostgreSQL, которое оставляет по одной строке на каждое уникальное значение expr, беря первую согласно ORDER BY. Это самый короткий способ решить задачу «последняя запись на пользователя» без оконных функций. Работает только в PostgreSQL: в MySQL, SQLite и стандартном SQL его нет, там применяют ROW_NUMBER() OVER (…) = 1. Ключевой момент — ORDER BY обязан начинаться с выражений DISTINCT ON, иначе выбор строки будет недетерминированным.

Синтаксис

SELECT DISTINCT ON (expr [, ...]) select_list FROM table ORDER BY expr [, ...], tie_breaker [ASC | DESC]

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

Пример 1: Последний заказ по каждому пользователю

SELECT DISTINCT ON (user_id)
  user_id, order_id, created_at, amount
FROM orders
ORDER BY user_id, created_at DESC;

Результат:

user_id | order_id | created_at          | amount
--------+----------+---------------------+-------
     17 |     2087 | 2026-07-10 14:22:01 |   1990
     42 |     2103 | 2026-07-11 09:05:44 |    499

Пример 2: Самый дешёвый заказ на пользователя

SELECT DISTINCT ON (user_id)
  user_id, order_id, amount
FROM orders
ORDER BY user_id, amount ASC;

Результат:

user_id | order_id | amount
--------+----------+-------
     17 |     1990 |    299
     42 |     2011 |    150

Пример 3: Последнее событие каждого типа

SELECT DISTINCT ON (user_id, event_name)
  user_id, event_name, created_at
FROM events
ORDER BY user_id, event_name, created_at DESC;

Результат:

user_id | event_name | created_at
--------+------------+--------------------
     17 | login      | 2026-07-11 08:00:00
     17 | purchase   | 2026-07-10 19:30:00
Порешать SQL-задачи в тренажёре →

Типичная ошибка

Только PostgreSQL — в MySQL, SQLite и стандартном SQL DISTINCT ON нет; там эмулируют через ROW_NUMBER() OVER (PARTITION BY … ORDER BY …) = 1. ORDER BY обязан начинаться с тех же выражений, что и DISTINCT ON, иначе «первая» строка выбирается недетерминированно. Направление сортировки по следующим ключам решает, какую именно строку из группы оставить: DESC даёт последнюю, ASC — первую.

Где спрашивают на собеседовании

Классический вопрос «последняя запись на пользователя» — на собесе просят и DISTINCT ON, и эквивалент через ROW_NUMBER(). Идёт в паре с ORDER BY и оконными функциями.

FAQ: частые вопросы про DISTINCT ON

Работает ли DISTINCT ON в MySQL или SQLite?

Нет, это специфичное расширение PostgreSQL. В MySQL, SQLite и стандартном SQL тот же результат получают через оконную функцию: ROW_NUMBER() OVER (PARTITION BY key ORDER BY sort_col DESC), а затем фильтруют по rn = 1 во внешнем запросе. Логика идентична DISTINCT ON.

Почему DISTINCT ON требует ORDER BY?

DISTINCT ON оставляет первую строку каждой группы, а «первая» определяется именно порядком сортировки. Поэтому ORDER BY должен начинаться с тех же выражений, что стоят в DISTINCT ON, а дальше идут поля, задающие приоритет внутри группы. Без согласованного ORDER BY PostgreSQL вернёт произвольную строку.

DISTINCT ON быстрее, чем ROW_NUMBER()?

Часто да: на PostgreSQL при наличии подходящего индекса DISTINCT ON выполняется одним проходом и не строит промежуточный номер строки. Но выигрыш зависит от данных и плана запроса, поэтому на больших таблицах стоит сравнить оба варианта через EXPLAIN ANALYZE.

Задачи в тренажёре по теме

Вопросы с собеседований по теме

Смежные темы SQL

Ранжирование · Оконные функции

Другие функции SQL

ROW_NUMBER · RANK · DENSE_RANK · LAG · LEAD · NTILE

Открыть SQL-тренажёр (565 задач) →