Собесов

LeetCode SQL — Customers Who Bought All Products: division по HAVING

SQLРеляционное делениеСредняяMiddle

Условие

Таблицы:

  • 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.

Подводные камни

  1. COUNT(*) vs COUNT(DISTINCT product_key). В Customer один клиент может купить продукт несколько раз — без DISTINCT счётчик задвоится, и фильтр HAVING сломается.
  2. Product может содержать дубли. В задаче PK есть, но если внезапно нет — (SELECT COUNT(DISTINCT product_key) FROM Product).
  3. 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».

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти