DISTINCT убирает дублирующиеся строки из результата запроса. Это одна из первых конструкций, с которой сталкивается каждый, кто работает с данными. Разберём как она работает, когда помогает, а когда лучше использовать альтернативы.
Базовый синтаксис
SELECT DISTINCT column1
FROM table_name;
DISTINCT применяется ко всему набору столбцов в SELECT, а не к одному из них.
Пример: список категорий
Таблица products:
| id | name | category | price |
|---|---|---|---|
| 1 | Ноутбук | Электроника | 85000 |
| 2 | Мышь | Электроника | 1500 |
| 3 | Стол | Мебель | 12000 |
| 4 | Кресло | Мебель | 25000 |
| 5 | Монитор | Электроника | 32000 |
Получить уникальные категории:
SELECT DISTINCT category FROM products;
Результат:
| category |
|---|
| Электроника |
| Мебель |
DISTINCT с несколькими столбцами
DISTINCT смотрит на комбинацию всех выбранных столбцов:
SELECT DISTINCT category, price
FROM products;
Вернёт уникальные пары (category, price). Если два товара одной категории по одной цене — одна из строк будет убрана.
COUNT(DISTINCT ...) — число уникальных значений
Самое частое применение DISTINCT — подсчёт уникальных значений:
-- Сколько разных категорий?
SELECT COUNT(DISTINCT category) AS unique_categories
FROM products;
-- Результат: 2
-- Сколько уникальных покупателей сделали заказ?
SELECT COUNT(DISTINCT user_id) AS unique_buyers
FROM orders;
Это работает и внутри GROUP BY:
-- Число уникальных покупателей по городу
SELECT city, COUNT(DISTINCT user_id) AS buyers
FROM orders
GROUP BY city;
DISTINCT vs GROUP BY
Часто DISTINCT и GROUP BY дают одинаковый результат:
-- Эквивалентные запросы:
SELECT DISTINCT category FROM products;
SELECT category FROM products GROUP BY category;
Когда предпочесть GROUP BY:
- Когда нужны агрегатные функции (COUNT, SUM, AVG)
- Когда нужен HAVING для фильтрации групп
- Обычно GROUP BY лучше оптимизируется планировщиком
Когда DISTINCT уместен:
- Когда просто нужны уникальные строки без агрегации
- Код короче и намерение очевиднее
NULL и DISTINCT
DISTINCT считает все NULL одинаковыми и оставляет только один NULL:
SELECT DISTINCT manager_id FROM employees;
-- Если несколько сотрудников без менеджера (NULL) — будет один NULL
Это важно: в обычном SQL NULL = NULL возвращает NULL (не TRUE), но DISTINCT группирует NULL вместе.
Производительность DISTINCT
DISTINCT требует сортировки или хеширования всего результата для поиска дубликатов — это дорого на больших таблицах.
-- Медленно на миллионе строк без индекса
SELECT DISTINCT user_id FROM events;
Что делать:
- Убедитесь, что на столбец есть индекс
- Если нужно только проверить существование — используйте EXISTS
- Если структура данных правильная — дубликатов не должно быть вовсе
-- Вместо COUNT(DISTINCT) на большой таблице
-- Иногда быстрее:
SELECT COUNT(*) FROM (
SELECT DISTINCT user_id FROM events
) sub;
DISTINCT ON — только в PostgreSQL
PostgreSQL поддерживает DISTINCT ON — выбрать уникальные строки по одному полю, сохраняя остальные:
-- Последний заказ каждого пользователя
SELECT DISTINCT ON (user_id)
user_id,
order_id,
created_at
FROM orders
ORDER BY user_id, created_at DESC;
DISTINCT ON (user_id) оставляет одну строку на каждый user_id. Какую именно — определяет ORDER BY. В данном случае — самую свежую.
Это мощная альтернатива ROW_NUMBER() + WHERE rn = 1:
-- Эквивалент через оконную функцию:
SELECT user_id, order_id, created_at
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
) t
WHERE rn = 1;
DISTINCT ON обычно немного быстрее и проще читается.
Антипаттерн: DISTINCT как заплатка
Нередко DISTINCT добавляют, чтобы «убрать дубликаты», не разобравшись почему они появились:
-- Плохо: зачем дубликаты вообще появились?
SELECT DISTINCT u.id, u.name
FROM users u
JOIN orders o ON u.id = o.user_id;
-- Каждый заказ создаёт отдельную строку — отсюда дубликаты
Правильное решение — либо использовать другой вид JOIN, либо переосмыслить запрос:
-- Хорошо: пользователи, у которых есть хоть один заказ
SELECT u.id, u.name
FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
Если DISTINCT нужен «чтобы убрать странные дубликаты» — это сигнал о проблеме в логике JOIN.
Итог
| Сценарий | Инструмент |
|---|---|
| Уникальные значения столбца | SELECT DISTINCT col |
| Число уникальных значений | COUNT(DISTINCT col) |
| Уникальные строки + агрегация | GROUP BY |
| Первая/последняя строка в группе | DISTINCT ON (PostgreSQL) |
| Проверить существование | EXISTS |
DISTINCT — простой и нужный инструмент. Главное — не использовать его как заплатку для скрытия проблем в запросе.