Клиенты, купившие все товары категории — задача без прямого SQL-перевода

Формулировка «клиент купил ВСЕ товары категории» звучит как фильтр, а на деле требует пересчёта в количества. В SQL нет оператора «для всех» — есть только агрегаты и сравнение чисел.

Идея: если в категории 12 товаров, то у клиента, купившего все, должно быть ровно 12 РАЗЛИЧНЫХ товаров этой категории в покупках. Значит задача сводится к двум подсчётам и их сравнению.

Сначала считаем общее число товаров в категории — это константа для сравнения. Потом для каждого клиента считаем количество различных товаров категории, которые он купил, через COUNT(DISTINCT ...). Остаётся сравнить два числа.

SELECT o.customer_id FROM orders o JOIN products p ON p.id = o.product_id WHERE p.category_id = :cat_id GROUP BY o.customer_id HAVING COUNT(DISTINCT o.product_id) = ( SELECT COUNT(*) FROM products WHERE category_id = :cat_id )

Частая ошибка — считать COUNT(*) вместо COUNT(DISTINCT product_id): если клиент купил один и тот же товар дважды, обычный счётчик его переоценит и условие сработает раньше времени. Вторая ловушка — забыть, что подзапрос с общим числом товаров нужно пересчитывать при каждом изменении каталога, то есть он не должен быть захардкожен.

Задачи вида «купил всё из набора» почти всегда решаются одной и той же схемой: переформулировать условие в сравнение двух чисел и посчитать оба агрегатами.

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки