Ось готовий урок, створений за твоїм майстер-промптом у стилі 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 виконує команди не так, як ви їх пишете зверху вниз.
Ось життєвий цикл вашого запиту:
- FROM (Звідки беремо дані?) — Беремо таблицю з усіма чеками.
- WHERE (Фільтруємо рядки) — Охоронець на вході: "Відкидаємо скасовані замовлення".
- GROUP BY (Групуємо) — Садимо гостей за столики: "Всі чеки клієнта А — в одну купу".
- AGGREGATION (Рахуємо) — Підбиваємо суму чеків для кожної купи.
- HAVING (Фільтруємо групи) — Менеджер залу: "Залишаємо тільки ті столики, де сума > 10 000".
- 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. 🧩 Підсумок
Отже, що ми маємо в сухому залишку:
- WHERE фільтрує рядки (вхід).
- HAVING фільтрує групи (вихід агрегатних функцій).
- Порядок: FROM -> WHERE -> GROUP BY -> HAVING -> SELECT.
Тепер ви вмієте не просто діставати дані, а робити справжню аналітику: знаходити бестселери, виявляти найкращих студентів чи підозрілі транзакції.
Що далі? Зараз ми працювали з однією таблицею. Але реальний світ складніший. Що, як ім'я студента лежить в одній таблиці, а оцінки — в іншій? Наступного разу ми поговоримо про супер-клей SQL — оператор JOIN.
А поки що — практикуйтеся! Це був CS50. 🏛️