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
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
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-задачи в тренажёре →
Классический вопрос «последняя запись на пользователя» — на собесе просят и DISTINCT ON, и эквивалент через ROW_NUMBER(). Идёт в паре с ORDER BY и оконными функциями.
Нет, это специфичное расширение 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 PostgreSQL вернёт произвольную строку.
Часто да: на PostgreSQL при наличии подходящего индекса DISTINCT ON выполняется одним проходом и не строит промежуточный номер строки. Но выигрыш зависит от данных и плана запроса, поэтому на больших таблицах стоит сравнить оба варианта через EXPLAIN ANALYZE.
Ранжирование · Оконные функции
ROW_NUMBER · RANK · DENSE_RANK · LAG · LEAD · NTILE
Открыть SQL-тренажёр (565 задач) →