Модуль 12

HAVING vs WHERE

Ось готовий урок, створений за твоїм майстер-промптом у стилі David Malan (CS50).


🎓 CS50: SQL — HAVING vs WHERE. Битва фільтрів

Привіт, друзі! Радий бачити вас знову.

Сьогодні ми розберемо тему, яка є класичним каменем спотикання для кожного початківця в базах даних. Це питання на співбесідах №1. Це причина сотень помилок типу Syntax Error.

Ми говоримо про різницю між WHERE та HAVING.


1. 🔥 Вступ: Проблема та мотивація

Уявіть, що ви аналітик у великому інтернет-магазині, наприклад, Rozetka або Amazon. Ваше завдання — знайти найактивніших покупців.

Ви пишете запит: "Покажи мені всіх користувачів, які витратили загалом більше 10 000 гривень".

Ви вже знаєте команду WHERE, правда? Вона чудово фільтрує: "Покажи замовлення за 2024 рік" або "Покажи товари дорожчі за 100 грн".

Але спробуйте написати: WHERE сума_всіх_покупок > 10000... і база даних "вибухне" помилкою. 💥

Чому? Уявіть собі фейс-контроль у нічному клубі. * WHERE — це охоронець на вході. Він перевіряє кожного окремо: "Тобі є 18? Проходь. Тобі немає? До побачення". Він бачить лише одну людину (один рядок) за раз. * Але ваше завдання — подивитися на групу людей всередині клубу і сказати: "Гей, ось ця компанія за столиком №5 витратила забагато, дайте їм знижку".

Охоронець на вході (WHERE) не знає, скільки компанія витратить у майбутньому, бо вони ще навіть не сіли за стіл!

Ось тут на сцену виходить HAVING. Без нього ви не зможете аналізувати підсумки, статистику та звіти.


2. 🧠 Теоретична база (Що там «під капотом»?)

Щоб зрозуміти різницю, треба зрозуміти одну річ: Порядок виконання. SQL виконує команди не так, як ви їх пишете зверху вниз.

Ось життєвий цикл вашого запиту:

  1. FROM (Звідки беремо дані?) — Беремо таблицю з усіма чеками.
  2. WHERE (Фільтруємо рядки) — Охоронець на вході: "Відкидаємо скасовані замовлення".
  3. GROUP BY (Групуємо) — Садимо гостей за столики: "Всі чеки клієнта А — в одну купу".
  4. AGGREGATION (Рахуємо) — Підбиваємо суму чеків для кожної купи.
  5. HAVING (Фільтруємо групи) — Менеджер залу: "Залишаємо тільки ті столики, де сума > 10 000".
  6. SELECT (Показуємо результат).

🔑 Головне правило:

  • WHERE працює з вихідними даними (сирими рядками) ДО групування.
  • HAVING працює з агрегованими даними (результатами обчислень) ПІСЛЯ групування.

Інтуїтивно: WHERE — це про деталі. HAVING — це про підсумки.


3. 🧪 Приклади (Coding Time!)

Припустимо, у нас є таблиця orders (замовлення): | id | customer_name | amount | status | |----|---------------|--------|---------| | 1 | Оля | 100 | completed | | 2 | Оля | 200 | completed | | 3 | Макс | 50 | completed | | 4 | Іван | 5000 | cancelled |

Приклад 1: Просто WHERE

Завдання: Знайти всі успішні замовлення на суму більше 80 грн.

SELECT *
FROM orders
WHERE amount > 80 AND status = 'completed';

Результат: Оля (100), Оля (200), Іван (відпав би, бо cancelled, Макс відпав, бо 50 < 80). Все просто. Охоронець перевірив кожен рядок.


Приклад 2: Помилка новачка ❌

Завдання: Знайти клієнтів, які сумарно витратили більше 250 грн.

-- ЦЕ НЕ СПРАЦЮЄ!
SELECT customer_name, SUM(amount)
FROM orders
WHERE SUM(amount) > 250 -- Помилка! SQL ще не знає суми в цей момент.
GROUP BY customer_name;

Приклад 3: Магія HAVING ✅

Виправляємо запит. Нам треба спочатку згрупувати, порахувати, а потім відсіяти.

SELECT customer_name, SUM(amount) as total_spent
FROM orders
WHERE status = 'completed'  -- 1. Спочатку прибираємо скасовані (оптимізація!)
GROUP BY customer_name      -- 2. Групуємо по людях
HAVING SUM(amount) > 250;   -- 3. Тепер фільтруємо за сумою

Що відбулося? 1. WHERE відкинув Івана (cancelled). 2. GROUP BY об'єднав Олю (100+200=300) і Макса (50). 3. HAVING подивився на числа 300 і 50. 300 > 250? Так. 50 > 250? Ні. Результат: Тільки Оля.


4. 🛠 Практична частина

Час "забруднити руки" кодом. Уявіть, що ви працюєте з базою даних університету. Таблиця exams: student_id, subject, score (оцінка 0-100).

Завдання 1 (Розігрів): Напишіть запит, який покаже середній бал (AVG(score)) для кожного студента. (Підказка: просто GROUP BY без умов).

Завдання 2 (WHERE): Модифікуйте запит: враховуйте тільки іспити з 'Math' та 'CS'. (Де ми фільтруємо предмети? До групування чи після?)

Завдання 3 (HAVING): Тепер покажіть тільки тих студентів, у яких середній бал вищий за 85.

Завдання 4 (Реальний кейс - Combo): Знайдіть студентів, які здавали 'CS', і отримали середній бал саме з CS нижче 60 (двієчники). (Потрібно і WHERE для предмету, і HAVING для середнього балу).

Завдання 5 (А що, якщо...): Що буде, якщо ми напишемо HAVING student_id = 5? Чи спрацює це? Чому це технічно можливо, але вважається поганим тоном?


5. 💡 Мислення як у розробника

Як досвідчені інженери приймають рішення, що використати?

1. Правило "Лінивого фільтра"

Ми хочемо відфільтрувати дані якнайраніше. Чим менше рядків дійде до важкої операції GROUP BY, тим швидше працюватиме база. * Якщо умову можна поставити в WHEREзавжди ставте її в WHERE. * Залишайте для HAVING тільки те, що неможливо порахувати без групування (суми, середні, кількості).

2. Типова помилка: "Все в HAVING"

Деякі новачки пхають все в HAVING: HAVING date > '2023-01-01' AND SUM(amount) > 100Це поганий код! 🤢 База спочатку згрупує всі роки (навіть 1990-й), витратить ресурси процесора, а потім просто викине зайве. Перенесіть дату в WHERE!

3. Читабельність

WHERE каже: "Я працюю з даними". HAVING каже: "Я працюю зі звітом". Пишіть так, щоб ваші колеги зрозуміли ваш намір.


6. 🧩 Підсумок

Отже, що ми маємо в сухому залишку:

  1. WHERE фільтрує рядки (вхід).
  2. HAVING фільтрує групи (вихід агрегатних функцій).
  3. Порядок: FROM -> WHERE -> GROUP BY -> HAVING -> SELECT.

Тепер ви вмієте не просто діставати дані, а робити справжню аналітику: знаходити бестселери, виявляти найкращих студентів чи підозрілі транзакції.

Що далі? Зараз ми працювали з однією таблицею. Але реальний світ складніший. Що, як ім'я студента лежить в одній таблиці, а оцінки — в іншій? Наступного разу ми поговоримо про супер-клей SQL — оператор JOIN.

А поки що — практикуйтеся! Це був CS50. 🏛️