В отчёте нужно посчитать число уникальных комбинаций клиента и товара. Почему сумму уникальных клиентов и уникальных товаров нельзя использовать как этот показатель?
Число уникальных комбинаций нужно считать по паре значений одновременно, а не складывать независимые количества уникальных клиентов и товаров. Один клиент может покупать несколько товаров, а один товар — встречаться у нескольких клиентов, поэтому такие множества пересекаются и их размеры не дают число пар.
Практически надёжный подход — сначала устранить дубли по двум столбцам, затем посчитать получившиеся строки. Если комбинация с NULL должна учитываться, подсчёт строк производной таблицы обычно сохраняет её как отдельную комбинацию.
Реляционная модель описывает данные как набор строк, а операции группировки и устранения дублей могут применяться сразу к нескольким атрибутам. Это позволяет рассматривать пару столбцов как составной признак, например ключ связи между клиентом и товаром.
Проблема возникла из-за различия между подсчётом уникальных значений отдельных измерений и подсчётом уникальных фактов. В аналитике факт «клиент купил товар» задаётся комбинацией атрибутов, поэтому каждый такой факт должен учитываться один раз.
Пусть один клиент купил три товара, а один из этих товаров купили ещё два клиента. Количество уникальных клиентов и количество уникальных товаров описывают разные множества, а не количество строк в множестве пар.
Неверный расчёт может не вызвать ошибку SQL, но даст правдоподобное числовое значение. Особенно опасна такая ошибка при расчёте количества связей, комбинаций, маршрутов или уникальных событий.
Сначала выбираются только нужные столбцы, затем применяется устранение дублей по их совместному значению, после чего результат считается обычным COUNT(*):
DISTINCT сравнивает строки по всей выбранной комбинации. Поэтому две строки с одинаковым клиентом и товаром превращаются в одну, но тот же клиент с другим товаром остаётся отдельной строкой.
Эквивалентный подход — сгруппировать данные по обоим столбцам во внутреннем запросе и посчитать группы во внешнем. Склеивать значения строковой конкатенацией нежелательно: разные пары могут дать одинаковую строку, а NULL, разделители, приведение типов и правила сортировки могут привести к дополнительным ошибкам.
Некоторые СУБД поддерживают специальный синтаксис подсчёта различных комбинаций нескольких столбцов, но его форма различается. Производная таблица с SELECT DISTINCT обычно понятнее и переносимее.
Важно заранее определить семантику NULL. В устранении дублей строки с одинаковым расположением NULL обычно считаются одной комбинацией, а комбинации с NULL и конкретным значением — разными. Если неполные пары не являются валидными фактами, их нужно исключить фильтрацией до устранения дублей.
В витрине покупок требовалось узнать число уникальных связей «клиент–товар» за месяц. Вариант со сложением количества уникальных клиентов и товаров был простым, но завышал результат: он измерял два независимых множества и не учитывал реальные пары.
Конкатенация идентификаторов казалась компактной, но была рискованной: например, значения с неоднозначными разделителями могли образовать одинаковый составной текст. Подсчёт через GROUP BY по двум столбцам был корректен, но потребовал дополнительного уровня запроса.
Выбрали устранение дублей во внутреннем запросе и COUNT(*) снаружи. Решение явно выражало бизнес-смысл, корректно учитывало составной признак и не зависело от особенностей функции подсчёта нескольких аргументов. После этого результат использовали как число уникальных связей, а не как сумму кардинальностей отдельных справочников.
Нет. Произведение показывает максимально возможное число комбинаций при условии, что каждый клиент может сочетаться с каждым товаром. В реальных данных присутствует только часть таких сочетаний, поэтому произведение обычно значительно отличается от фактического количества пар.
Это определяется смыслом данных, а не только синтаксисом запроса. Если NULL означает неизвестный, но существующий идентификатор и такую строку требуется учитывать, её оставляют до этапа DISTINCT. Если NULL означает неполный или некорректный факт, применяют фильтр по обоим идентификаторам до устранения дублей.
Конкатенация может привести к коллизиям представления: разные пары превращаются в одну строку из-за разделителей, форматов или преобразований типов. Хеширование также теоретически допускает коллизии и требует явно определённой обработки NULL. Сравнение двух исходных столбцов через DISTINCT или GROUP BY не создаёт такой искусственной неоднозначности.