SQLDISTINCTtutorial

Что такое DISTINCT в SQL? Уникальные значения для начинающих

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

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

DISTINCT — это команда «не возвращай повторяющиеся строки». Самый простой способ получить уникальные значения колонки или комбинации колонок: список стран, в которых живут пользователи, список тегов на блоге, набор уникальных дат заказов.

Без DISTINCT запрос вернёт всё подряд — и если у тебя 1000 заказов из 50 стран, то 1000 строк со страной. С DISTINCT — 50 строк, по одной на страну.

Зачем нужен DISTINCT

Самый частый сценарий: «дай мне все уникальные значения этой колонки», без счёта, без агрегатов, просто список. DISTINCT отвечает на это в одну строку:

  • Список регионов, откуда заходят пользователи (для UI-фильтра).
  • Список тегов в блоге (для облака тегов).
  • Уникальные даты, по которым были продажи.

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

SELECT DISTINCT country FROM users;

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

Пример с таблицей

Таблица users:

id name country
1 Аня RU
2 Боб US
3 Вера RU
4 Гриша RU
5 Дима US
6 Лена BY

Без DISTINCT:

SELECT country FROM users;
country
RU
US
RU
RU
US
BY

С DISTINCT:

SELECT DISTINCT country FROM users;
country
RU
US
BY

Шесть строк превратились в три — по одной на уникальную страну.

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

SELECT DISTINCT country, tier FROM users;

Уникальной считается пара (country, tier). Если есть (RU, free) и ещё одна (RU, free) — отсеется одна. Но (RU, free) и (RU, gold) — это разные пары, обе останутся.

Результат на нашей таблице (если у Ани, Веры, Гриши tier разные):

country tier
RU free
RU gold
US free
BY gold

DISTINCT vs GROUP BY

Часто их путают. Оба «схлопывают» дубликаты:

  • SELECT DISTINCT country FROM users — даёт уникальные страны.
  • SELECT country FROM users GROUP BY country — то же самое.

Если не нужен агрегат, по производительности и читаемости они эквивалентны. Большинство планировщиков (включая Postgres) выполняют их одинаково.

Когда нужен агрегат — только GROUP BY:

-- DISTINCT не подходит, нужен COUNT
SELECT country, COUNT(*) FROM users GROUP BY country;

Правило: если просто уникальныеDISTINCT короче и яснее. Если уникальные + счётчикGROUP BY.

DISTINCT с NULL

Все NULL'ы в DISTINCT считаются одним и тем же значением. Если у тебя 5 строк с email IS NULL, в DISTINCT email будет одна строка NULL:

-- В таблице 100 строк, у 30 email = NULL, у 70 — заполнен (50 уникальных)
SELECT DISTINCT email FROM users;
-- → 51 строка: 50 уникальных email + одна NULL

Если хочется «без NULL вообще» — WHERE email IS NOT NULL.

DISTINCT ON — Postgres-фишка

PostgreSQL поддерживает DISTINCT ON (column) — «по одной строке на каждое уникальное значение column». Это уже не «убери дубликаты», а «оставь первую из каждой группы».

-- Самый свежий заказ каждого клиента
SELECT DISTINCT ON (customer_id) *
FROM orders
ORDER BY customer_id, created_at DESC;

Логика:

  1. Postgres сортирует строки по customer_id, created_at DESC.
  2. Для каждого уникального customer_id берёт первую в этой сортировке (т.е. с самой свежей датой).
  3. Возвращает только эти строки — по одной на клиента.

Это типичный паттерн «топ-1 в каждой группе». В стандартном SQL аналогичное делается через ROW_NUMBER + подзапрос — длиннее.

DISTINCT ON есть только в PostgreSQL. Не работает в MySQL, SQLite, MSSQL.

DISTINCT внутри агрегата

В агрегатных функциях можно использовать DISTINCT:

-- Сколько уникальных юзеров что-то заказывали
SELECT COUNT(DISTINCT customer_id) FROM orders;

Это считает уникальные значения customer_id, а не все строки таблицы. Самый частый use case в аналитике.

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

1. DISTINCT действует на ВСЕ колонки в SELECT. Думаешь «уберу дубликаты только по country», пишешь SELECT DISTINCT country, name FROM users — но (RU, Аня) и (RU, Вера) это разные пары, дубликаты не схлопнутся. Если хочешь уникальное по одной колонке — пиши SELECT DISTINCT country без name.

2. DISTINCT не сортирует. SELECT DISTINCT country FROM users вернёт страны в произвольном порядке. Хочешь алфавитно — ORDER BY country явно.

3. Производительность на больших таблицах. DISTINCT требует от БД отсортировать (или построить хеш) по всему набору. На таблице 100M строк может занять долго. Часто помогает индекс на колонке.

4. COUNT(DISTINCT *) не работает. Можно COUNT(*) или COUNT(DISTINCT column) — но не COUNT(DISTINCT *). Для подсчёта уникальных строк целиком — оборачивай в подзапрос:

SELECT COUNT(*) FROM (SELECT DISTINCT * FROM users) t;

5. NULL = NULL в DISTINCT. В большинстве сравнений SQL NULL = NULL даёт NULL (= не равно). Но в DISTINCT (а также в GROUP BY) NULL'ы группируются вместе. Это исключение, и о нём часто забывают.

6. DISTINCT ON без правильного ORDER BY. Для DISTINCT ON (customer_id) сортировка должна начинаться с customer_id. ORDER BY created_at DESC без customer_id даст ошибку. Правильно: ORDER BY customer_id, created_at DESC.

Мини-резюме

  • DISTINCT убирает дубликаты — возвращает уникальные строки.
  • По умолчанию учитывает все колонки в SELECT. Хочешь по одной — клади только её.
  • NULL'ы в DISTINCT = одно значение. Если хочешь без NULL — WHERE column IS NOT NULL.
  • DISTINCT без агрегатов ≈ GROUP BY без агрегатов. Используй DISTINCT для краткости, GROUP BY — когда нужен COUNT/SUM/AVG.
  • DISTINCT ON (col) — Postgres-фишка для «топ-1 в каждой группе». В сочетании с правильным ORDER BY.

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

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

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