Как работает DISTINCT в SQL? В каких случаях его стоит и не стоит использовать?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
DISTINCT в SQL: удаление дубликатов и оптимизация
DISTINCT — это ключевое слово в SQL, которое удаляет дублирующиеся строки из результирующего набора. Кажется просто, но есть множество нюансов и ситуаций, где его использование может быть неправильным или неэффективным.
Базовый синтаксис и примеры
Простой DISTINCT
-- Все значения с дубликатами
SELECT country FROM users
ORDER BY country
LIMIT 10
-- Результат:
-- US
-- US
-- CA
-- UK
-- US
-- С DISTINCT (только уникальные)
SELECT DISTINCT country FROM users
ORDER BY country
-- Результат:
-- CA
-- UK
-- US
DISTINCT с несколькими столбцами
-- DISTINCT применяется ко всей строке, не отдельно к каждому столбцу
SELECT DISTINCT country, platform FROM users
-- Результат (уникальные комбинации):
-- US, web
-- US, mobile
-- CA, web
-- UK, mobile
Важно помнить: DISTINCT удаляет дубликаты всей строки, а не отдельных столбцов.
Как работает DISTINCT внутри
Когда ты пишешь DISTINCT, база данных:
- Выбирает все строки из запроса
- Сортирует их (обычно по hash или полностью)
- Удаляет соседние дубликаты
- Возвращает результат
-- DISTINCT требует SORT, что дорого
EXPLAIN ANALYZE SELECT DISTINCT country FROM users
-- Результат покажет:
-- -> HashAggregate (cost=..., rows=...) или
-- -> Sort Distinct (cost=..., rows=...)
Когда СТОИТ использовать DISTINCT
1. Подсчёт уникальных значений (но COUNT(DISTINCT) лучше)
-- Плохо: долгий результат
SELECT DISTINCT user_id FROM orders
-- Хорошо: быстрый результат
SELECT COUNT(DISTINCT user_id) FROM orders
2. Удаление явных дубликатов в данных
-- Есть ошибка в данных: пользователи дублируются
SELECT user_id FROM customers
-- Результат:
-- 1
-- 1
-- 2
-- 3
-- 3
-- Удалить дубликаты
SELECT DISTINCT user_id FROM customers
3. Проверка, сколько уникальных значений
-- Сколько уникальных стран использует платформа?
SELECT DISTINCT country FROM users
-- Результат даст представление о разнообразии
-- Лучший вариант:
SELECT COUNT(DISTINCT country) as countries_count FROM users
4. Когда тебе нужно один раз в месяц вычислить список уникальных
-- Создать справочник уникальных городов один раз в месяц
SELECT DISTINCT city FROM users
WHERE created_at >= CURRENT_DATE - INTERVAL 30 DAY
Когда НЕ стоит использовать DISTINCT
1. Для подсчёта вместо COUNT(DISTINCT)
-- Неправильно и медленно
SELECT COUNT(*) FROM (
SELECT DISTINCT user_id FROM orders
) t
-- Правильно и быстро
SELECT COUNT(DISTINCT user_id) FROM orders
Почему? Первый вариант создаёт промежуточную таблицу, требует сортировки. COUNT(DISTINCT) напрямую.
2. В WHERE вместо IN с подзапросом
-- Плохо
SELECT * FROM orders
WHERE user_id IN (
SELECT DISTINCT user_id FROM users WHERE country = 'US'
)
-- Хорошо
SELECT * FROM orders
WHERE user_id IN (
SELECT user_id FROM users WHERE country = 'US'
)
-- SQL оптимизатор сам удалит дубликаты
3. С GROUP BY (они делают одно и то же)
-- Плохо: двойная работа
SELECT DISTINCT product_id, category FROM products
GROUP BY product_id, category
-- Хорошо: одного достаточно
SELECT product_id, category FROM products
GROUP BY product_id, category
-- Или если нужны агрегаты:
SELECT product_id, category, COUNT(*) as count FROM products
GROUP BY product_id, category
4. На ОЧЕНЬ больших таблицах без фильтра
-- Медленно: сканирует всю таблицу, затем сортирует (10 млн строк)
SELECT DISTINCT user_id FROM events
-- Это может заморозить БД
-- Лучше: сначала отфильтруй
SELECT DISTINCT user_id FROM events
WHERE created_at >= CURRENT_DATE - INTERVAL 30 DAY
LIMIT 100000
Оптимизация DISTINCT запросов
1. Используй GROUP BY вместо DISTINCT
-- DISTINCT (сортировка)
SELECT DISTINCT user_id, country FROM users
-- Execution: Sort Distinct
-- GROUP BY (обычно быстрее на больших таблицах)
SELECT user_id, country FROM users
GROUP BY user_id, country
-- Execution: HashAggregate (быстрее на 30-40%)
2. Добавляй фильтры WHERE перед DISTINCT
-- Медленно: сортирует все 10 млн пользователей
SELECT DISTINCT country FROM users
-- Быстро: сортирует только активных 500k
SELECT DISTINCT country FROM users
WHERE last_active >= CURRENT_DATE - INTERVAL 90 DAY
3. Используй LIMIT при необходимости
-- Только первые 100 уникальных стран
SELECT DISTINCT country FROM users
LIMIT 100
-- PostgreSQL может остановиться рано, не сортируя всё
4. Добавляй индексы на столбцы DISTINCT
-- Если часто запрашиваешь DISTINCT по country
CREATE INDEX idx_users_country ON users(country)
-- PostgreSQL может использовать индекс, не сканируя всю таблицу
Практические примеры анализа
Пример 1: Уникальные пользователи по странам
-- Правильно для анализа
SELECT
country,
COUNT(*) as user_count,
COUNT(DISTINCT user_id) as unique_users -- если есть дубликаты
FROM users
WHERE created_at >= CURRENT_DATE - INTERVAL 30 DAY
GROUP BY country
ORDER BY unique_users DESC
Пример 2: Активные потребители маркетплейса
-- Найти уникальные потребителя с покупками
SELECT DISTINCT consumer_id
FROM orders
WHERE created_at >= CURRENT_DATE - INTERVAL 30 DAY
AND status = 'completed'
-- Лучше: если нужна агрегация
SELECT
consumer_id,
COUNT(*) as orders,
SUM(amount) as total_spent
FROM orders
WHERE created_at >= CURRENT_DATE - INTERVAL 30 DAY
AND status = 'completed'
GROUP BY consumer_id
ORDER BY total_spent DESC
Пример 3: Чистка дублей перед JOIN
-- Есть дубликаты в таблице
SELECT COUNT(*) FROM customers -- 1,000,000
SELECT COUNT(DISTINCT customer_id) FROM customers -- 990,000
-- Перед объединением
WITH clean_customers AS (
SELECT DISTINCT ON (customer_id) customer_id, name, email
FROM customers
ORDER BY customer_id, created_at DESC
)
SELECT * FROM clean_customers
JOIN orders ON clean_customers.customer_id = orders.customer_id
Как мониторить производительность
-- Проверить, сколько времени занимает DISTINCT
EXPLAIN ANALYZE
SELECT DISTINCT user_id FROM events WHERE created_at >= CURRENT_DATE - 30
-- Посмотри на строку "Actual time" — если больше 1 сек, переделай запрос
Интерпретация:
- Planning Time: < 1ms (хорошо)
- Execution Time: < 100ms (хорошо)
- Execution Time: 1-5 сек (плохо, нужно оптимизировать)
- Execution Time: > 5 сек (очень плохо, переписать)
Рекомендации для Product Analysts
- Используй COUNT(DISTINCT) для подсчётов, не SELECT DISTINCT + COUNT
- Добавляй WHERE фильтры, чтобы снизить объём данных перед DISTINCT
- Используй GROUP BY когда нужна агрегация, а не DISTINCT
- Проверяй EXPLAIN ANALYZE, если запрос медленный
- На больших таблицах (> 1M строк) часто приходится переписывать DISTINCT запросы
Сравнительная таблица
| Задача | Решение | Производительность |
|---|---|---|
| Подсчитать уникальные user_id | COUNT(DISTINCT user_id) | Отлично |
| Получить список уникальных user_id | SELECT DISTINCT user_id | Хорошо |
| Уникальные + агрегация | SELECT ... GROUP BY | Отлично |
| Очень много уникальных на большой таблице | DISTINCT + WHERE + LIMIT | Хорошо |
| Удаление дубликатов перед JOIN | SELECT DISTINCT ON или ROW_NUMBER() | Зависит |
Главный вывод: DISTINCT — это инструмент для удаления дубликатов, но обычно есть более эффективный способ сделать то же самое с GROUP BY или COUNT(DISTINCT). Используй DISTINCT только когда других вариантов нет.