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;
Результат:
Повторы исчезли. Было 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 встречается два раза, но это нормально. Потому что строки разные:
Для DISTINCT это две разные комбинации.
Если вам нужны только страны, не добавляйте tier в SELECT:
SELECT DISTINCT country
FROM users;
Если нужны уникальные пары страна-тариф, тогда добавляйте обе колонки:
SELECT DISTINCT country, tier
FROM users;
DISTINCT и сортировка
DISTINCT убирает дубликаты, но не гарантирует красивый порядок.
Например:
SELECT DISTINCT country
FROM users;
Результат может прийти так:
А может иначе:
База данных не обязана сортировать результат просто потому, что вы написали DISTINCT.
Если нужен понятный порядок, добавляйте ORDER BY:
SELECT DISTINCT country
FROM users
ORDER BY country;
Теперь результат будет отсортирован по стране:
Запомните простое правило:
DISTINCT отвечает за уникальность, ORDER BY отвечает за порядок.
Это разные задачи.
DISTINCT и NULL
NULL в SQL означает отсутствие значения или неизвестное значение. С ним всегда нужно быть внимательным.
Пусть есть такая таблица:
Запрос:
SELECT DISTINCT email
FROM users;
вернёт:
Что здесь важно:
- повторяющийся
anna@example.com остался один раз;
- два
NULL тоже превратились в один NULL.
Хотя в обычных сравнениях NULL ведёт себя особым образом, в DISTINCT все NULL в одной колонке считаются одним повторяющимся значением.
Если вы хотите получить только заполненные email, добавьте фильтр:
SELECT DISTINCT email
FROM users
WHERE email IS NOT NULL;
Результат:
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;
Результат:
А COUNT(DISTINCT customer_id) посчитает уникальных покупателей:
SELECT COUNT(DISTINCT customer_id) AS unique_customers
FROM orders;
Результат:
Почему 3? Потому что покупатели были такие:
Заказов 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;
вернёт:
Потому что уникальные непустые значения — это 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;
Как это работает:
- База сортирует строки по
customer_id.
- Внутри каждого клиента сортирует заказы от новых к старым по
created_at DESC.
- Для каждого
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 нужен, когда вы хотите сказать базе данных: «Покажи мне не все строки подряд, а только разные варианты».
DISTINCTв SQL — это ключевое слово, которое говорит базе:Обычно
SELECTвозвращает все подходящие строки. Если в таблице десять пользователей из одной страны, то страна появится в результате десять раз. Иногда это как раз нужно. Но часто нам нужен не весь список строк, а только набор уникальных значений: страны, города, категории, теги, даты заказов.Вот здесь и помогает
DISTINCT.Например, у нас есть 1000 пользователей из 50 стран. Обычный запрос вернёт 1000 строк со странами, включая повторы. А запрос с
DISTINCTвернёт 50 строк — по одной на каждую уникальную страну.Зачем нужен DISTINCT
DISTINCTчаще всего используют, когда нужно получить справочник значений из обычной таблицы.Например:
Допустим, вы делаете фильтр на сайте:
Эти страны можно не хранить отдельно, а получить из таблицы пользователей:
SELECT DISTINCT country FROM users;База посмотрит на все значения в колонке
country, уберёт повторения и вернёт аккуратный список.Базовый синтаксис DISTINCT
Самый простой вариант:
SELECT DISTINCT country FROM users;Читается так:
Важно:
DISTINCTпишется сразу послеSELECT.Не так:
SELECT country DISTINCT FROM users;А так:
SELECT DISTINCT country FROM users;Пример с таблицей пользователей
Пусть есть таблица
users:Сначала сделаем обычный запрос без
DISTINCT:SELECT country FROM users;Результат:
База честно вернула значение
countryиз каждой строки. В таблице 6 пользователей — значит, в результате 6 строк.Теперь добавим
DISTINCT:SELECT DISTINCT country FROM users;Результат:
Повторы исчезли. Было 6 строк, стало 3.
Это и есть главная идея
DISTINCT: оставить только уникальные строки результата.DISTINCT работает не с таблицей, а с результатом SELECT
Очень важный момент:
DISTINCTубирает повторы после того, как сформирован результатSELECT.Например:
SELECT DISTINCT country FROM users;Здесь результат состоит только из одной колонки
country. Значит, уникальность проверяется только по стране.А теперь другой запрос:
SELECT DISTINCT country, name FROM users;Здесь результат состоит уже из двух колонок:
countryиname.Значит, уникальной считается не страна отдельно, а вся пара:
Если в таблице есть пользователи
AnnaизRUиVeraизRU, это разные строки результата:Они не схлопнутся в одну строку, потому что отличаются по колонке
name.Отсюда главное правило:
DISTINCTдействует на все колонки, которые указаны вSELECT.DISTINCT по нескольким колонкам
Допустим, в таблице пользователей есть страна и тариф:
Запрос:
SELECT DISTINCT country, tier FROM users;вернёт уникальные сочетания страны и тарифа:
Обратите внимание:
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;Результат может прийти так:
А может иначе:
База данных не обязана сортировать результат просто потому, что вы написали
DISTINCT.Если нужен понятный порядок, добавляйте
ORDER BY:SELECT DISTINCT country FROM users ORDER BY country;Теперь результат будет отсортирован по стране:
Запомните простое правило:
DISTINCTотвечает за уникальность,ORDER BYотвечает за порядок.Это разные задачи.
DISTINCT и NULL
NULLв SQL означает отсутствие значения или неизвестное значение. С ним всегда нужно быть внимательным.Пусть есть такая таблица:
Запрос:
SELECT DISTINCT email FROM users;вернёт:
Что здесь важно:
anna@example.comостался один раз;NULLтоже превратились в одинNULL.Хотя в обычных сравнениях
NULLведёт себя особым образом, вDISTINCTвсеNULLв одной колонке считаются одним повторяющимся значением.Если вы хотите получить только заполненные email, добавьте фильтр:
SELECT DISTINCT email FROM users WHERE email IS NOT NULL;Результат:
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;Результат:
Здесь
DISTINCTуже не решает задачу, потому что нам нужно не просто убрать повторы, а посчитать строки внутри каждой группы.Простое правило:
DISTINCT;GROUP BY.DISTINCT внутри COUNT
DISTINCTможно использовать не только послеSELECT, но и внутри агрегатных функций.Самый частый пример:
SELECT COUNT(DISTINCT customer_id) AS unique_customers FROM orders;Этот запрос считает, сколько уникальных покупателей есть в таблице заказов.
Допустим, есть таблица
orders:Обычный
COUNTпосчитает все заказы:SELECT COUNT(*) AS orders_count FROM orders;Результат:
А
COUNT(DISTINCT customer_id)посчитает уникальных покупателей:SELECT COUNT(DISTINCT customer_id) AS unique_customers FROM orders;Результат:
Почему 3? Потому что покупатели были такие:
10;20;30.Заказов 5, а уникальных покупателей 3.
Это очень частый приём в аналитике: посчитать не количество событий, а количество уникальных пользователей, клиентов, устройств, сессий или товаров.
COUNT(DISTINCT) и NULL
Есть ещё один важный нюанс:
COUNT(DISTINCT column)не считаетNULL.Пусть есть таблица:
Запрос:
SELECT COUNT(DISTINCT customer_id) AS unique_customers FROM orders;вернёт:
Потому что уникальные непустые значения — это
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:Нужно получить по одному заказу на каждого клиента — самый новый.
В PostgreSQL можно написать так:
SELECT DISTINCT ON (customer_id) id, customer_id, amount, created_at FROM orders ORDER BY customer_id, created_at DESC;Как это работает:
customer_id.created_at DESC.customer_idоставляет первую строку.Результат:
Получили самый свежий заказ каждого клиента.
Важное правило для 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
Короткая шпаргалка:
DISTINCTDISTINCTпо двум колонкамGROUP BYCOUNT(DISTINCT customer_id)EXISTSилиDISTINCTпослеJOINDISTINCT ONROW_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нужен, когда вы хотите сказать базе данных: «Покажи мне не все строки подряд, а только разные варианты».