В отчёте нужно посчитать число уникальных комбинаций клиента и товара. Почему сумму уникальных клиентов и у...

В отчёте нужно посчитать число уникальных комбинаций клиента и товара. Почему сумму уникальных клиентов и уникальных товаров нельзя использовать как этот показатель?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Число уникальных комбинаций нужно считать по паре значений одновременно, а не складывать независимые количества уникальных клиентов и товаров. Один клиент может покупать несколько товаров, а один товар — встречаться у нескольких клиентов, поэтому такие множества пересекаются и их размеры не дают число пар.

Практически надёжный подход — сначала устранить дубли по двум столбцам, затем посчитать получившиеся строки. Если комбинация с NULL должна учитываться, подсчёт строк производной таблицы обычно сохраняет её как отдельную комбинацию.

Исторический контекст

Реляционная модель описывает данные как набор строк, а операции группировки и устранения дублей могут применяться сразу к нескольким атрибутам. Это позволяет рассматривать пару столбцов как составной признак, например ключ связи между клиентом и товаром.

Проблема возникла из-за различия между подсчётом уникальных значений отдельных измерений и подсчётом уникальных фактов. В аналитике факт «клиент купил товар» задаётся комбинацией атрибутов, поэтому каждый такой факт должен учитываться один раз.

Постановка проблемы

Пусть один клиент купил три товара, а один из этих товаров купили ещё два клиента. Количество уникальных клиентов и количество уникальных товаров описывают разные множества, а не количество строк в множестве пар.

Неверный расчёт может не вызвать ошибку SQL, но даст правдоподобное числовое значение. Особенно опасна такая ошибка при расчёте количества связей, комбинаций, маршрутов или уникальных событий.

Подробное решение

Сначала выбираются только нужные столбцы, затем применяется устранение дублей по их совместному значению, после чего результат считается обычным COUNT(*):

SELECT COUNT(*) AS unique_pairs FROM ( SELECT DISTINCT client_id, product_id FROM purchases ) AS pairs;

DISTINCT сравнивает строки по всей выбранной комбинации. Поэтому две строки с одинаковым клиентом и товаром превращаются в одну, но тот же клиент с другим товаром остаётся отдельной строкой.

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

Некоторые СУБД поддерживают специальный синтаксис подсчёта различных комбинаций нескольких столбцов, но его форма различается. Производная таблица с SELECT DISTINCT обычно понятнее и переносимее.

Важно заранее определить семантику NULL. В устранении дублей строки с одинаковым расположением NULL обычно считаются одной комбинацией, а комбинации с NULL и конкретным значением — разными. Если неполные пары не являются валидными фактами, их нужно исключить фильтрацией до устранения дублей.

Ситуация из практики

В витрине покупок требовалось узнать число уникальных связей «клиент–товар» за месяц. Вариант со сложением количества уникальных клиентов и товаров был простым, но завышал результат: он измерял два независимых множества и не учитывал реальные пары.

Конкатенация идентификаторов казалась компактной, но была рискованной: например, значения с неоднозначными разделителями могли образовать одинаковый составной текст. Подсчёт через GROUP BY по двум столбцам был корректен, но потребовал дополнительного уровня запроса.

Выбрали устранение дублей во внутреннем запросе и COUNT(*) снаружи. Решение явно выражало бизнес-смысл, корректно учитывало составной признак и не зависело от особенностей функции подсчёта нескольких аргументов. После этого результат использовали как число уникальных связей, а не как сумму кардинальностей отдельных справочников.

Что кандидаты часто упускают

  1. Можно ли заменить подсчёт уникальных пар произведением числа уникальных клиентов и товаров?

Нет. Произведение показывает максимально возможное число комбинаций при условии, что каждый клиент может сочетаться с каждым товаром. В реальных данных присутствует только часть таких сочетаний, поэтому произведение обычно значительно отличается от фактического количества пар.

  1. Как определить, нужно ли считать комбинации с NULL?

Это определяется смыслом данных, а не только синтаксисом запроса. Если NULL означает неизвестный, но существующий идентификатор и такую строку требуется учитывать, её оставляют до этапа DISTINCT. Если NULL означает неполный или некорректный факт, применяют фильтр по обоим идентификаторам до устранения дублей.

  1. Почему нельзя надёжно считать уникальные пары через хеш или конкатенацию?

Конкатенация может привести к коллизиям представления: разные пары превращаются в одну строку из-за разделителей, форматов или преобразований типов. Хеширование также теоретически допускает коллизии и требует явно определённой обработки NULL. Сравнение двух исходных столбцов через DISTINCT или GROUP BY не создаёт такой искусственной неоднозначности.