Что такое DISTINCT ON в PostgreSQL?

MiddlePostgreSQL · Backend·Обновлено 12 августа 2026
Коротко
DISTINCT ON — расширение PostgreSQL, позволяющее оставить по одной строке для каждого уникального значения указанного выражения, выбирая конкретную строку из группы через ORDER BY.

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 вторым столбцом результат непредсказуем

Лучшие курсы по теме

изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Node.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее