SQLDISTINCTtutorial

Что такое DISTINCT в SQL?

DISTINCT — это «убери дубликаты». Простыми словами: уникальные значения колонки или комбинации колонок, разница с GROUP BY, поведение с NULL и Postgres-фишка DISTINCT ON для «по одной строке на группу». С таблицами before/after и частыми ошибками.

13 мин чтенияСправочникSQL · DISTINCT · tutorial

DISTINCT в SQL — это ключевое слово, которое говорит базе:

Верни только уникальные строки. Повторы убери.

Обычно SELECT возвращает все подходящие строки. Если в таблице десять пользователей из одной страны, то страна появится в результате десять раз. Иногда это как раз нужно. Но часто нам нужен не весь список строк, а только набор уникальных значений: страны, города, категории, теги, даты заказов.

Вот здесь и помогает DISTINCT.

Например, у нас есть 1000 пользователей из 50 стран. Обычный запрос вернёт 1000 строк со странами, включая повторы. А запрос с DISTINCT вернёт 50 строк — по одной на каждую уникальную страну.

Зачем нужен DISTINCT

DISTINCT чаще всего используют, когда нужно получить справочник значений из обычной таблицы.

Например:

  • список стран, в которых живут пользователи;
  • список городов для фильтра в интерфейсе;
  • список уникальных тегов в блоге;
  • список дат, когда были заказы;
  • список категорий товаров;
  • список пользователей, которые хотя бы раз сделали заказ.

Допустим, вы делаете фильтр на сайте:

Страна: Россия, США, Беларусь, Германия...

Эти страны можно не хранить отдельно, а получить из таблицы пользователей:

SELECT DISTINCT country
FROM users;

База посмотрит на все значения в колонке country, уберёт повторения и вернёт аккуратный список.

Базовый синтаксис DISTINCT

Самый простой вариант:

SELECT DISTINCT country
FROM users;

Читается так:

Возьми колонку country из таблицы users и верни каждое значение только один раз.

Важно: DISTINCT пишется сразу после SELECT.

Не так:

SELECT country DISTINCT
FROM users;

А так:

SELECT DISTINCT country
FROM users;

Пример с таблицей пользователей

Пусть есть таблица users:

id name country
1 Anna RU
2 Bob US
3 Vera RU
4 Gleb RU
5 Dan US
6 Lena BY

Сначала сделаем обычный запрос без DISTINCT:

SELECT country
FROM users;

Результат:

country
RU
US
RU
RU
US
BY

База честно вернула значение country из каждой строки. В таблице 6 пользователей — значит, в результате 6 строк.

Теперь добавим DISTINCT:

SELECT DISTINCT country
FROM users;

Результат:

country
RU
US
BY

Повторы исчезли. Было 6 строк, стало 3.

Это и есть главная идея DISTINCT: оставить только уникальные строки результата.

DISTINCT работает не с таблицей, а с результатом SELECT

Очень важный момент: DISTINCT убирает повторы после того, как сформирован результат SELECT.

Например:

SELECT DISTINCT country
FROM users;

Здесь результат состоит только из одной колонки country. Значит, уникальность проверяется только по стране.

А теперь другой запрос:

SELECT DISTINCT country, name
FROM users;

Здесь результат состоит уже из двух колонок: country и name.

Значит, уникальной считается не страна отдельно, а вся пара:

country + name

Если в таблице есть пользователи Anna из RU и Vera из RU, это разные строки результата:

country name
RU Anna
RU Vera

Они не схлопнутся в одну строку, потому что отличаются по колонке name.

Отсюда главное правило:

DISTINCT действует на все колонки, которые указаны в SELECT.

DISTINCT по нескольким колонкам

Допустим, в таблице пользователей есть страна и тариф:

id name country tier
1 Anna RU free
2 Bob US free
3 Vera RU gold
4 Gleb RU free
5 Dan US free
6 Lena BY gold

Запрос:

SELECT DISTINCT country, tier
FROM users;

вернёт уникальные сочетания страны и тарифа:

country tier
RU free
US free
RU gold
BY gold

Обратите внимание: RU встречается два раза, но это нормально. Потому что строки разные:

  • RU + free;
  • RU + gold.

Для DISTINCT это две разные комбинации.

Если вам нужны только страны, не добавляйте tier в SELECT:

SELECT DISTINCT country
FROM users;

Если нужны уникальные пары страна-тариф, тогда добавляйте обе колонки:

SELECT DISTINCT country, tier
FROM users;

DISTINCT и сортировка

DISTINCT убирает дубликаты, но не гарантирует красивый порядок.

Например:

SELECT DISTINCT country
FROM users;

Результат может прийти так:

country
RU
US
BY

А может иначе:

country
BY
RU
US

База данных не обязана сортировать результат просто потому, что вы написали DISTINCT.

Если нужен понятный порядок, добавляйте ORDER BY:

SELECT DISTINCT country
FROM users
ORDER BY country;

Теперь результат будет отсортирован по стране:

country
BY
RU
US

Запомните простое правило:

DISTINCT отвечает за уникальность, ORDER BY отвечает за порядок.

Это разные задачи.

DISTINCT и NULL

NULL в SQL означает отсутствие значения или неизвестное значение. С ним всегда нужно быть внимательным.

Пусть есть такая таблица:

id name email
1 Anna anna@example.com
2 Bob NULL
3 Vera vera@example.com
4 Gleb NULL
5 Dan anna@example.com

Запрос:

SELECT DISTINCT email
FROM users;

вернёт:

email
anna@example.com
NULL
vera@example.com

Что здесь важно:

  • повторяющийся anna@example.com остался один раз;
  • два NULL тоже превратились в один NULL.

Хотя в обычных сравнениях NULL ведёт себя особым образом, в DISTINCT все NULL в одной колонке считаются одним повторяющимся значением.

Если вы хотите получить только заполненные email, добавьте фильтр:

SELECT DISTINCT email
FROM users
WHERE email IS NOT NULL;

Результат:

email
anna@example.com
vera@example.com

DISTINCT и GROUP BY: в чём разница

Новички часто путают DISTINCT и GROUP BY, потому что в простых случаях они дают похожий результат.

Вот запрос с DISTINCT:

SELECT DISTINCT country
FROM users;

А вот похожий запрос через GROUP BY:

SELECT country
FROM users
GROUP BY country;

Оба вернут уникальные страны.

Тогда зачем нужны два способа?

Разница в намерении.

DISTINCT говорит:

Просто убери повторы.

GROUP BY говорит:

Разбей строки на группы, чтобы по каждой группе что-то посчитать.

Если вам нужен просто список уникальных стран, лучше использовать DISTINCT:

SELECT DISTINCT country
FROM users;

Это короче и читается яснее.

А если нужно посчитать пользователей по странам, нужен GROUP BY:

SELECT country, COUNT(*) AS users_count
FROM users
GROUP BY country;

Результат:

country users_count
RU 3
US 2
BY 1

Здесь DISTINCT уже не решает задачу, потому что нам нужно не просто убрать повторы, а посчитать строки внутри каждой группы.

Простое правило:

  • нужны просто уникальные значения — DISTINCT;
  • нужны уникальные группы и расчёты по ним — GROUP BY.

DISTINCT внутри COUNT

DISTINCT можно использовать не только после SELECT, но и внутри агрегатных функций.

Самый частый пример:

SELECT COUNT(DISTINCT customer_id) AS unique_customers
FROM orders;

Этот запрос считает, сколько уникальных покупателей есть в таблице заказов.

Допустим, есть таблица orders:

id customer_id amount
1 10 100.00
2 10 250.00
3 20 80.00
4 30 500.00
5 20 120.00

Обычный COUNT посчитает все заказы:

SELECT COUNT(*) AS orders_count
FROM orders;

Результат:

orders_count
5

А COUNT(DISTINCT customer_id) посчитает уникальных покупателей:

SELECT COUNT(DISTINCT customer_id) AS unique_customers
FROM orders;

Результат:

unique_customers
3

Почему 3? Потому что покупатели были такие:

  • 10;
  • 20;
  • 30.

Заказов 5, а уникальных покупателей 3.

Это очень частый приём в аналитике: посчитать не количество событий, а количество уникальных пользователей, клиентов, устройств, сессий или товаров.

COUNT(DISTINCT) и NULL

Есть ещё один важный нюанс: COUNT(DISTINCT column) не считает NULL.

Пусть есть таблица:

id customer_id
1 10
2 10
3 NULL
4 20
5 NULL

Запрос:

SELECT COUNT(DISTINCT customer_id) AS unique_customers
FROM orders;

вернёт:

unique_customers
2

Потому что уникальные непустые значения — это 10 и 20.

NULL не попадёт в подсчёт, потому что COUNT(column) в SQL не считает NULL.

Почему COUNT(DISTINCT *) обычно не пишут

Иногда новичок хочет посчитать количество уникальных строк целиком и пытается написать:

SELECT COUNT(DISTINCT *)
FROM users;

Так обычно нельзя.

Можно считать все строки:

SELECT COUNT(*)
FROM users;

Можно считать уникальные значения конкретной колонки:

SELECT COUNT(DISTINCT email)
FROM users;

А если нужно посчитать уникальные строки целиком, можно использовать подзапрос:

SELECT COUNT(*)
FROM (
  SELECT DISTINCT *
  FROM users
) AS unique_rows;

Сначала внутренний запрос убирает повторяющиеся строки, потом внешний считает, сколько их осталось.

На практике чаще считают уникальность по конкретной колонке или набору колонок, а не по всей строке.

DISTINCT ON в PostgreSQL

В PostgreSQL есть особая возможность — DISTINCT ON.

Это не стандартный SQL, а удобная фишка PostgreSQL.

Обычный DISTINCT говорит:

Убери одинаковые строки.

А DISTINCT ON говорит:

Оставь по одной строке на каждую группу, а какую именно строку оставить — определим через сортировку.

Классический пример: найти самый свежий заказ каждого клиента.

Пусть есть таблица orders:

id customer_id amount created_at
1 10 100.00 2026-01-10 10:00:00
2 10 250.00 2026-02-15 12:00:00
3 20 80.00 2026-01-20 09:00:00
4 20 120.00 2026-03-01 18:00:00
5 30 500.00 2026-02-01 14:00:00

Нужно получить по одному заказу на каждого клиента — самый новый.

В PostgreSQL можно написать так:

SELECT DISTINCT ON (customer_id)
  id,
  customer_id,
  amount,
  created_at
FROM orders
ORDER BY customer_id, created_at DESC;

Как это работает:

  1. База сортирует строки по customer_id.
  2. Внутри каждого клиента сортирует заказы от новых к старым по created_at DESC.
  3. Для каждого customer_id оставляет первую строку.

Результат:

id customer_id amount created_at
2 10 250.00 2026-02-15 12:00:00
4 20 120.00 2026-03-01 18:00:00
5 30 500.00 2026-02-01 14:00:00

Получили самый свежий заказ каждого клиента.

Важное правило для DISTINCT ON

Для DISTINCT ON порядок в ORDER BY очень важен.

Если пишем:

SELECT DISTINCT ON (customer_id)
  id,
  customer_id,
  amount,
  created_at
FROM orders
ORDER BY customer_id, created_at DESC;

то всё хорошо: ORDER BY начинается с customer_id, то есть с той же колонки, которая указана в DISTINCT ON.

А вот так писать нельзя:

SELECT DISTINCT ON (customer_id)
  id,
  customer_id,
  amount,
  created_at
FROM orders
ORDER BY created_at DESC;

В PostgreSQL для DISTINCT ON (customer_id) сортировка должна начинаться с customer_id.

Иначе база не сможет корректно выбрать первую строку внутри каждой группы.

Запомните:

DISTINCT ON (customer_id)
ORDER BY customer_id, created_at DESC

Это правильная связка.

Как сделать то же самое без DISTINCT ON

DISTINCT ON есть в PostgreSQL, но его нет в MySQL, SQL Server и SQLite.

В более универсальном SQL задачу «одна лучшая строка из каждой группы» часто решают через оконную функцию ROW_NUMBER.

Пример:

SELECT id, customer_id, amount, created_at
FROM (
  SELECT
    id,
    customer_id,
    amount,
    created_at,
    ROW_NUMBER() OVER (
      PARTITION BY customer_id
      ORDER BY created_at DESC
    ) AS row_num
  FROM orders
) AS ranked_orders
WHERE row_num = 1;

Здесь логика такая:

  • PARTITION BY customer_id разбивает заказы по клиентам;
  • ORDER BY created_at DESC ставит новые заказы выше старых;
  • ROW_NUMBER() нумерует строки внутри каждого клиента;
  • WHERE row_num = 1 оставляет только первый заказ каждого клиента.

В PostgreSQL DISTINCT ON часто короче и приятнее. Но ROW_NUMBER более универсален и лучше переносится между разными СУБД.

Производительность DISTINCT

DISTINCT выглядит маленьким словом, но для базы это не всегда маленькая работа.

Чтобы убрать дубликаты, базе нужно понять, какие строки одинаковые. Обычно она делает это одним из способов:

  • сортирует строки и убирает соседние повторы;
  • строит хеш-таблицу уникальных значений.

На маленьких таблицах это почти незаметно.

На таблице в десятки или сотни миллионов строк DISTINCT может стать тяжёлым запросом, особенно если уникальность считается по нескольким колонкам.

Например:

SELECT DISTINCT country
FROM users;

может работать быстро, если пользователей немного.

А вот такой запрос на огромной таблице событий может быть тяжёлым:

SELECT DISTINCT user_id
FROM events;

Если таблица events большая, базе нужно просмотреть очень много строк.

Что может помочь:

  • индекс по колонке, по которой ищем уникальные значения;
  • фильтр WHERE, чтобы уменьшить объём данных;
  • заранее подготовленная агрегированная таблица для аналитики;
  • понимание, действительно ли нужны все уникальные значения прямо сейчас.

Например, лучше так:

SELECT DISTINCT user_id
FROM events
WHERE created_at >= DATE '2026-01-01';

чем без фильтра по всей истории, если вам нужны только пользователи за конкретный период.

DISTINCT не лечит плохой JOIN

Иногда DISTINCT используют как пластырь.

Например, написали JOIN, получили дубликаты и быстро добавили DISTINCT, чтобы «стало красиво»:

SELECT DISTINCT users.id, users.email
FROM users
JOIN orders ON orders.user_id = users.id;

Иногда это нормально. Например, если задача действительно такая: получить пользователей, у которых есть хотя бы один заказ.

Но часто дубликаты после JOIN — это сигнал, что нужно разобраться в связи таблиц.

Например, один пользователь может иметь много заказов. Поэтому при соединении users и orders строка пользователя повторится столько раз, сколько у него заказов.

Если вам нужны пользователи, у которых есть заказы, можно написать через EXISTS:

SELECT users.id, users.email
FROM users
WHERE EXISTS (
  SELECT 1
  FROM orders
  WHERE orders.user_id = users.id
);

Так запрос прямо говорит:

Верни пользователей, для которых существует хотя бы один заказ.

DISTINCT полезен, но не стоит использовать его вслепую, чтобы скрывать непонимание результата JOIN.

Частые ошибки новичков

Ошибка 1. Думать, что DISTINCT работает только по первой колонке

Новичок пишет:

SELECT DISTINCT country, name
FROM users;

и ожидает одну строку на страну.

Но DISTINCT смотрит на всю строку результата. Значит, уникальной считается пара country и name.

Если нужны только уникальные страны, пишите:

SELECT DISTINCT country
FROM users;

Ошибка 2. Добавлять лишние колонки в SELECT

Допустим, нужен список уникальных стран.

Правильно:

SELECT DISTINCT country
FROM users;

А вот так результат изменится:

SELECT DISTINCT country, id
FROM users;

Поскольку id почти всегда уникален, каждая строка станет уникальной. В итоге DISTINCT почти ничего не уберёт.

Если добавили в SELECT уникальный идентификатор, не ждите, что строки схлопнутся по стране.

Ошибка 3. Ждать сортировку от DISTINCT

DISTINCT не обязан сортировать результат для пользователя.

Плохо полагаться на случайный порядок:

SELECT DISTINCT country
FROM users;

Лучше явно написать:

SELECT DISTINCT country
FROM users
ORDER BY country;

Ошибка 4. Забывать про NULL

В DISTINCT несколько NULL превращаются в один NULL.

SELECT DISTINCT email
FROM users;

Если в таблице много строк без email, в результате будет одна строка с NULL.

Если она не нужна, добавьте:

SELECT DISTINCT email
FROM users
WHERE email IS NOT NULL;

Ошибка 5. Использовать DISTINCT вместо GROUP BY с агрегатами

Такой запрос не решает задачу подсчёта пользователей по странам:

SELECT DISTINCT country, COUNT(*)
FROM users;

Если нужно посчитать строки внутри групп, используйте GROUP BY:

SELECT country, COUNT(*) AS users_count
FROM users
GROUP BY country;

Ошибка 6. Писать COUNT(DISTINCT *) вместо подзапроса

Нельзя просто написать:

SELECT COUNT(DISTINCT *)
FROM users;

Если нужно посчитать уникальные строки целиком, используйте подзапрос:

SELECT COUNT(*)
FROM (
  SELECT DISTINCT *
  FROM users
) AS unique_rows;

А если нужна уникальность по конкретной колонке, пишите так:

SELECT COUNT(DISTINCT email)
FROM users;

Ошибка 7. Неправильно использовать DISTINCT ON

В PostgreSQL такой запрос неправильный:

SELECT DISTINCT ON (customer_id)
  id,
  customer_id,
  created_at
FROM orders
ORDER BY created_at DESC;

Для DISTINCT ON (customer_id) сортировка должна начинаться с customer_id.

Правильно:

SELECT DISTINCT ON (customer_id)
  id,
  customer_id,
  created_at
FROM orders
ORDER BY customer_id, created_at DESC;

Ошибка 8. Закрывать DISTINCT проблему в JOIN

Если после JOIN появились дубликаты, не всегда нужно сразу ставить DISTINCT.

Сначала стоит понять, почему строки размножились.

Например, это естественно:

SELECT users.id, users.email, orders.id AS order_id
FROM users
JOIN orders ON orders.user_id = users.id;

Если у пользователя пять заказов, он появится пять раз — по одному разу на каждый заказ.

Это не ошибка JOIN, а нормальный результат связи «один ко многим».

Как выбрать: DISTINCT, GROUP BY или EXISTS

Короткая шпаргалка:

Задача Что использовать
Получить уникальные страны DISTINCT
Получить уникальные пары страна-тариф DISTINCT по двум колонкам
Посчитать пользователей по странам GROUP BY
Посчитать уникальных покупателей COUNT(DISTINCT customer_id)
Найти клиентов, у которых есть заказы EXISTS или DISTINCT после JOIN
Взять самый свежий заказ каждого клиента в PostgreSQL DISTINCT ON
Взять самый свежий заказ каждого клиента универсально ROW_NUMBER

Как читать DISTINCT-запрос

Возьмём запрос:

SELECT DISTINCT country, tier
FROM users
WHERE country IS NOT NULL
ORDER BY country, tier;

Его можно прочитать так:

Возьми пользователей, у которых страна заполнена.
Верни уникальные сочетания страны и тарифа.
Отсортируй результат по стране и тарифу.

Если вы умеете так переводить SQL на обычный язык, значит, вы понимаете не только синтаксис, но и смысл запроса.

Главное

DISTINCT убирает повторяющиеся строки из результата SELECT.

Самое важное:

  • DISTINCT пишется после SELECT;
  • он возвращает только уникальные строки;
  • уникальность проверяется по всем колонкам, указанным в SELECT;
  • если указать одну колонку, получите уникальные значения этой колонки;
  • если указать несколько колонок, получите уникальные комбинации этих колонок;
  • DISTINCT не гарантирует сортировку — для порядка нужен ORDER BY;
  • несколько NULL в DISTINCT считаются одним значением;
  • COUNT(DISTINCT column) считает количество уникальных непустых значений;
  • для подсчётов по группам нужен GROUP BY;
  • DISTINCT ON — возможность PostgreSQL для выбора первой строки из каждой группы;
  • на больших таблицах DISTINCT может быть тяжёлым, потому что базе нужно найти и убрать дубликаты.

Если совсем коротко: DISTINCT нужен, когда вы хотите сказать базе данных: «Покажи мне не все строки подряд, а только разные варианты».

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр