ProgrammazioneBI/аналитик, Data Engineer

Как корректно реализовать подсчёт уникальных пользователей по сложному набору критериев с учётом NULL и дубликатов, используя конструкции DISTINCT, COUNT, GROUP BY в SQL?

Supera i colloqui con l'assistente IA Hintsage

Ответ.

История вопроса

Отчёты по уникальным пользователям необходимы для аналитики и статистики. Однако в реальных данных часто встречаются дубликаты аккаунтов, NULL-значения (например, неуказанный email), и требуется учитывать различные критерии (например, уникальность по имени, почте, ip, а иногда их комбинации).

Проблема

Типичная ошибка — считать COUNT(DISTINCT user_id), не учитывая, что в нужных столбцах есть NULL или неочевидные дубликаты (например, один человек с разными email, или несколько строк с одним user_id и разным статусом). Сложные запросы с GROUP BY могут давать неверный результат, если не продумана логика уникальности.

Решение

Важно объединять конструкции DISTINCT, GROUP BY и фильтрацию NULL. Иногда требуется предварительно подготовить данные в CTE или вложенном подзапросе, сгруппировав по нужному множеству признаков.

Пример кода:

-- Подсчет уникальных пользователей по email и ip, игнорируя NULL SELECT COUNT(*) AS unique_users FROM ( SELECT DISTINCT email, ip_address FROM users WHERE email IS NOT NULL AND ip_address IS NOT NULL ) u;

Ключевые особенности:

  • DISTINCT не учитывает строки с NULL в любом из перечисленных столбцов
  • Для кросс-группировки лучше использовать GROUP BY по комбинации полей
  • При работе с дубликатами данные часто нужно предварительно "очистить"

Вопросы с подвохом.

COUNT(DISTINCT ...) учитывает строки с NULL?

Нет: если хотя бы один из столбцов в списке DISTINCT имеет значение NULL, такая комбинация считается отдельной уникальной (NULL не равен NULL по SQL-стандарту). Обычно удобнее сперва убрать NULL фильтрацией.


Можно ли сравнивать NULL с NULL через DISTINCT?

В SQL каждая пара NULL-значений считается разной, поэтому каждая строка с NULL в любом из колонок будет считаться отдельной. Нужно применять фильтрацию через IS NOT NULL.


GROUP BY всегда даёт такой же результат, как DISTINCT?

Нет: GROUP BY создаёт по одной строке на каждую неповторяющуюся комбинацию значений, а DISTINCT — просто удаляет дубликаты. В ряде случаев результат разный, особенно если применяется агрегация.

Типовые ошибки и анти-паттерны

  • Неочевидная фильтрация NULL-значений
  • Неявное дублирование данных передAggregating
  • Некорректная комбинация DISTINCT с агрегацией на разных уровнях иерархии

Пример из жизни

Негативный кейс

Бизнес-аналитик строит отчёт по уникальным клиентам через COUNT(DISTINCT user_id), а user_id в реальности может быть NULL или дублироваться (например, временные аккаунты). Фактическое число пользователей получается больше реального — искажённые показатели в отчёте.

Плюсы:

  • Быстрая реализация отчёта

Минусы:

  • Некорректные бизнес-решения из-за неверной метрики

Позитивный кейс

Аналитик предварительно очищает данные, отфильтровывает NULL и явные дубликаты в подзапросах, а также использует SET-операции для сложных критериев уникальности.

Плюсы:

  • Корректные и прозрачные метрики
  • Валидные бизнес-решения и KPI

Минусы:

  • Более сложный запрос, требуется качество проверки на тестовых данных