Что такое DISTINCT ON в PostgreSQL?
DISTINCT ON в PostgreSQL
DISTINCT ON — это нестандартное расширение синтаксиса SELECT, специфичное для PostgreSQL. Оно позволяет оставить ровно одну строку для каждой уникальной комбинации значений в скобках, при этом давая контроль над тем, какая именно строка будет выбрана из группы дубликатов.
Базовый синтаксис
SELECT DISTINCT ON (выражение) столбцы
FROM таблица
ORDER BY выражение, [критерий_выбора];
Ключевое правило: столбцы в DISTINCT ON должны идти первыми в ORDER BY. PostgreSQL сначала сортирует строки по ORDER BY, затем из каждой группы с одинаковым DISTINCT ON значением берёт первую строку.
Пример: последний заказ каждого клиента
-- Получить самый свежий заказ для каждого клиента
SELECT DISTINCT ON (customer_id)
customer_id,
order_id,
created_at,
total_amount
FROM orders
ORDER BY customer_id, created_at DESC;
Здесь для каждого customer_id будет выбрана строка с максимальным created_at — потому что ORDER BY сортирует по дате убывающе, и из группы берётся первая.
Сравнение с GROUP BY
GROUP BY требует агрегации остальных столбцов, DISTINCT ON — нет:
-- GROUP BY: нужна агрегация, теряем detail-столбцы
SELECT customer_id, MAX(created_at) AS last_order_date
FROM orders
GROUP BY customer_id;
-- order_id и total_amount здесь не получить без подзапроса
-- DISTINCT ON: все столбцы из «победившей» строки доступны
SELECT DISTINCT ON (customer_id)
customer_id, order_id, created_at, total_amount
FROM orders
ORDER BY customer_id, created_at DESC;
Сравнение с ROW_NUMBER()
-- Эквивалент через оконную функцию (стандартный SQL)
SELECT customer_id, order_id, created_at, total_amount
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_at DESC) AS rn
FROM orders
) sub
WHERE rn = 1;
-- DISTINCT ON лаконичнее и зачастую быстрее за счёт отсутствия подзапроса
Производительность
Для эффективной работы DISTINCT ON нужен индекс, покрывающий ORDER BY:
-- Индекс для запроса выше
CREATE INDEX idx_orders_customer_date
ON orders (customer_id, created_at DESC);
При наличии такого индекса PostgreSQL использует Index Scan и обрабатывает каждую группу без полной сортировки всей таблицы.
Ограничения
- Не входит в стандарт SQL — код непереносим на MySQL, MSSQL, Oracle.
- Нельзя использовать DISTINCT ON и обычный DISTINCT одновременно.
- ORDER BY должен начинаться именно с тех выражений, что указаны в DISTINCT ON.
Что хочет услышать интервьюер
Кандидат понимает, что DISTINCT ON — PostgreSQL-специфичный синтаксис, а не стандартный SQL
Объяснение механизма: сначала сортировка по ORDER BY, затем выбор первой строки из каждой группы
Понимание разницы с GROUP BY (не нужна агрегация, доступны все столбцы выбранной строки)
Сравнение с ROW_NUMBER() OVER (PARTITION BY ...) как стандартной альтернативой
Знание о важности индекса на столбцы из ORDER BY для производительности
Пример: Последняя запись на каждого пользователя
-- Получить самый свежий заказ для каждого клиента
SELECT DISTINCT ON (customer_id)
customer_id,
order_id,
created_at,
total_amount
FROM orders
ORDER BY customer_id, created_at DESC;
-- Индекс для ускорения запроса
CREATE INDEX idx_orders_customer_date
ON orders (customer_id, created_at DESC);
Пример: Сравнение с ROW_NUMBER()
-- Через DISTINCT ON (PostgreSQL-специфично, лаконично)
SELECT DISTINCT ON (customer_id)
customer_id, order_id, created_at
FROM orders
ORDER BY customer_id, created_at DESC;
-- Через ROW_NUMBER (стандартный SQL, переносимо)
SELECT customer_id, order_id, created_at
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY created_at DESC
) AS rn
FROM orders
) sub
WHERE rn = 1;
Пример: DISTINCT ON по нескольким столбцам
-- Первый товар каждой категории в каждом магазине по цене
SELECT DISTINCT ON (store_id, category_id)
store_id,
category_id,
product_id,
price
FROM products
ORDER BY store_id, category_id, price ASC;
Типичные ошибки
Путаница порядка: забывают, что столбцы DISTINCT ON обязаны стоять первыми в ORDER BY
Считают, что DISTINCT ON работает как обычный DISTINCT — убирает полные дубликаты строк
Не осознают непереносимость на другие СУБД и используют в кросс-платформенных проектах без обёртки
Игнорируют индексирование и удивляются медленной работе на больших таблицах
Не понимают детерминированность: без явного ORDER BY вторым столбцом результат непредсказуем


