In relational databases, the GROUP BY and HAVING clauses in MySQL are powerful tools used to organize and filter data when working with aggregates. In this comprehensive tutorial, we will explore how these clauses work—first using single tables, and then how they can be combined with joins for deeper insights.
We’ll use the following sample tables for all our examples:
👨💼 employees Table
| employee_id | name | department_id | manager_id |
|---|---|---|---|
| 1 | Alice | 1 | NULL |
| 2 | Bob | 2 | 1 |
| 3 | Charlie | 3 | 1 |
| 4 | David | NULL | 2 |
| 5 | Eva | 2 | 2 |
🏢 departments Table
| department_id | department_name |
| 1 | HR |
| 2 | IT |
| 4 | Marketing |
💻 laptops Table
| laptop_id | employee_id | brand |
| 1 | 1 | Dell |
| 2 | 2 | HP |
| 3 | 3 | Lenovo |
| 4 | 5 | Apple |
🔹 GROUP BY with Single Table
Example 1: Count of Employees per Department (from employees table only)
SELECT department_id, COUNT(*) AS total_employees
FROM employees
GROUP BY department_id;
Output:
| department_id | total_employees |
| 1 | 1 |
| 2 | 2 |
| 3 | 1 |
| NULL | 1 |
Example 2: Number of Reportees per Manager (from employees table)
SELECT manager_id, COUNT(*) AS total_reportees
FROM employees
GROUP BY manager_id;
Output:
| manager_id | total_reportees |
| 1 | 2 |
| 2 | 2 |
| NULL | 1 |
Example 3: Count of Each Laptop Brand (from laptops table only)
SELECT brand, COUNT(*) AS brand_count
FROM laptops
GROUP BY brand;
Output:
| brand | brand_count |
| Dell | 1 |
| HP | 1 |
| Lenovo | 1 |
| Apple | 1 |
🔹 HAVING with Single Table
Example 4: Managers with More Than 1 Reportee (from employees table)
SELECT manager_id, COUNT(*) AS total_reportees
FROM employees
GROUP BY manager_id
HAVING total_reportees > 1;
Output:
| manager_id | total_reportees |
| 1 | 2 |
| 2 | 2 |
🔸 GROUP BY and HAVING with JOINs
Example 5: Department Name with Employee Count
SELECT d.department_name, COUNT(e.employee_id) AS total_employees
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
GROUP BY d.department_name;
Output:
| department_name | total_employees |
| HR | 1 |
| IT | 2 |
| NULL | 1 |
| Marketing | 0 |
Example 6: Departments with More Than 1 Employee
SELECT d.department_name, COUNT(e.employee_id) AS total_employees
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
GROUP BY d.department_name
HAVING total_employees > 1;
Output:
| department_name | total_employees |
| IT | 2 |
Example 7: Count of Laptop Brands per Department
SELECT d.department_name, l.brand, COUNT(*) AS count
FROM laptops l
JOIN employees e ON l.employee_id = e.employee_id
JOIN departments d ON e.department_id = d.department_id
GROUP BY d.department_name, l.brand;
Output:
| department_name | brand | count |
| HR | Dell | 1 |
| IT | Apple | 1 |
| IT | HP | 1 |
Example 8: Departments With More Than One Laptop Brand
SELECT d.department_name, COUNT(DISTINCT l.brand) AS unique_brands
FROM laptops l
JOIN employees e ON l.employee_id = e.employee_id
JOIN departments d ON e.department_id = d.department_id
GROUP BY d.department_name
HAVING unique_brands > 1;
Output:
| department_name | unique_brands |
| IT | 2 |
Example 9: Top Manager with Most Employees
SELECT e.manager_id, COUNT(*) AS total_reportees
FROM employees e
GROUP BY e.manager_id
HAVING total_reportees = (
SELECT MAX(manager_count)
FROM (
SELECT manager_id, COUNT(*) AS manager_count
FROM employees
GROUP BY manager_id
) AS subquery
);
Output:
| manager_id | total_reportees |
| 1 | 2 |
| 2 | 2 |
Summary
- ✅
GROUP BYgroups rows for aggregation. - ✅
HAVINGfilters groups after aggregation. - ✅ Both can be used on single tables or with JOINs.
- ✅ Useful for reporting on employee distribution, laptop allocation, and managerial performance.
Practice Exercises
- Find departments with fewer than 2 employees.
- List managers who have more than one direct report.
- Count how many employees have a laptop of each brand.
- Identify which department has the most laptop brands.
Use these examples to practice and master GROUP BY and HAVING in MySQL!
