Условие
Таблицы:
Customer(customer_id, product_key)— покупки.Product(product_key)— справочник всех продуктов.
Верните customer_id тех, кто купил каждый продукт из Product.
Решение
HAVING COUNT(DISTINCT) = total products
SELECT customer_id
FROM Customer
GROUP BY customer_id
HAVING COUNT(DISTINCT product_key) = (SELECT COUNT(*) FROM Product);Что здесь происходит
Если в Product 3 продукта, а у клиента — COUNT(DISTINCT product_key) = 3, то он купил всё.
Этот приём — каноническое «реляционное деление» в SQL без оператора DIVIDE (которого нет в стандарте).
Эквивалент через двойной NOT EXISTS
SELECT DISTINCT c.customer_id
FROM Customer c
WHERE NOT EXISTS (
SELECT 1 FROM Product p
WHERE NOT EXISTS (
SELECT 1 FROM Customer c2
WHERE c2.customer_id = c.customer_id AND c2.product_key = p.product_key
)
);«Для клиента не существует продукта, который он бы не купил». Читается как ужас, но это true division.
Подводные камни
COUNT(*)vsCOUNT(DISTINCT product_key). В Customer один клиент может купить продукт несколько раз — без DISTINCT счётчик задвоится, и фильтр HAVING сломается.- Product может содержать дубли. В задаче PK есть, но если внезапно нет —
(SELECT COUNT(DISTINCT product_key) FROM Product). - Customer купил продукт, которого нет в Product. Это «лишние» строки — фильтр всё равно требует
>= |Product|, но через GROUP они дадут больше. ПравильнееJOINс Product илиWHERE product_key IN (SELECT product_key FROM Product).
Эталонный ответ
GROUP BY customer_id HAVING COUNT(DISTINCT product_key) = (SELECT COUNT(*) FROM Product). Лаконичный SQL-«divide».