Combining Aggregation Techniques
Progress Tracking
Log in to save this lesson and continue from where you left off.
Putting It All Together
You’ve learned the pieces: aggregate functions, GROUP BY, HAVING, NULL handling, conditional aggregation. Real analysis usually requires combining several of these in one query.
Let’s walk through building a complex query step by step.
Building Queries Incrementally
Here’s a business question: Find departments with more than 2 employees hired after 2022, showing their headcount and average salary, sorted by average salary.
Don’t need to write it all at once. Build it piece by piece, testing after each step.
Headcount by Department
| id | first_name | last_name | department | salary | phone_number | joining_date |
|---|---|---|---|---|---|---|
| 1 | Sarah | Mitchell | HR | 95000 | 555-0101 | 2021-03-15 |
| 2 | Michael | Chen | HR | 88000 | 555-0102 | 2022-06-01 |
| 3 | Emily | Rodriguez | HR | 82500 | 2021-09-20 | |
| 4 | David | Park | HR | 80000 | 555-0104 | 2023-01-10 |
| 5 | Lisa | Thompson | HR | 65000 | 2021-04-05 |
Count how many employees are in each department.
Run it. Check the output makes sense before moving to the next step.
Step 2: Only Hires After 2022
Now add the row-level filter.
Oracle doesn’t implicitly cast string literals to dates — use a DATE literal or TO_DATE(). The other three dialects accept the plain string format directly.
Add a WHERE clause to keep only employees who joined after 2022-01-01.
Now you’re only counting recent hires. Check that the numbers changed.
Step 3: Departments with More Than 2 Such Hires
Add HAVING to keep only departments with more than 2 employees.
Only departments meeting the threshold show up now.
Step 4: Add Salary Stats and Sort
Add average, minimum, and maximum salary columns and sort by average salary descending.
Now you’ve answered the full business question. Each step was testable on its own.
Always build queries incrementally. Run after each change. It’s much easier to fix a bug in the step you just added than to debug a 20-line query that doesn’t work.
Combining GROUP BY with Conditional Aggregation
You can use CASE WHEN inside grouped queries for detailed breakdowns:
SELECT
department,
COUNT(*) AS total,
SUM(CASE WHEN salary < 80000 THEN 1 ELSE 0 END) AS under_80k,
SUM(CASE WHEN salary >= 80000 THEN 1 ELSE 0 END) AS over_80k,
ROUND(AVG(salary), 0) AS avg_salary
FROM techcorp_workforce
GROUP BY department
ORDER BY total DESC;Each department row now shows the salary distribution within that department.
| user_id | created_at | company_id | language | activated_at | state |
|---|---|---|---|---|---|
| 11 | 2013-01-01 04:41:13 | 1 | german | 2013-01-01 | active |
| 52 | 2013-01-05 15:30:45 | 2866 | spanish | 2013-01-05 | active |
| 52 | 2013-01-05 15:30:45 | 2866 | german | 2013-01-05 | active |
| 108 | 2013-01-10 11:04:58 | 1848 | spanish | 2013-01-10 | active |
| 167 | 2013-01-16 20:40:24 | 6709 | arabic | 2013-01-16 | active |
Find companies with 2 or more Chinese-speaking users. Use WHERE, GROUP BY, HAVING, and ORDER BY together.
The Query Building Checklist
When writing complex aggregation queries, follow this order:
- Start with
SELECTandFROM - Add
WHEREif you need to filter individual rows - Add
GROUP BYwith the columns you want to segment by - Add
HAVINGif you need to filter based on aggregate values - Add
ORDER BYto sort your results - Test after each step
Top 10 Songs 2010
Filter by year with WHERE, group appropriately, then use ORDER BY and LIMIT.
| year | year_rank | group_name | artist | song_name | id |
|---|---|---|---|---|---|
| 1956 | 1 | Elvis Presley | Elvis Presley | Heartbreak Hotel | 1 |
| 1956 | 2 | Elvis Presley | Elvis Presley | Don't Be Cruel | 2 |
| 1956 | 3 | Nelson Riddle | Nelson Riddle | Lisbon Antigua | 3 |
| 1956 | 4 | Platters | Platters | My Prayer | 4 |
| 1956 | 5 | Gogi Grant | Gogi Grant | The Wayward Wind | 5 |
Find the top 10 ranked songs in 2010. Output the rank, group name, and song name, but do not show the same song twice. Sort the result based on the rank in ascending order.
Department Workforce Analysis
Filter with WHERE before grouping, then use HAVING to filter groups.
| id | first_name | last_name | department | salary | phone_number | joining_date |
|---|---|---|---|---|---|---|
| 1 | Sarah | Mitchell | HR | 95000 | 555-0101 | 2021-03-15 |
| 2 | Michael | Chen | HR | 88000 | 555-0102 | 2022-06-01 |
| 3 | Emily | Rodriguez | HR | 82500 | 2021-09-20 | |
| 4 | David | Park | HR | 80000 | 555-0104 | 2023-01-10 |
| 5 | Lisa | Thompson | HR | 65000 | 2021-04-05 |
The workforce planning team is analyzing department growth since the company's expansion, focusing on teams that have grown substantially. For each department with 5 or more employees hired after 2020, return the `name`, `headcount`, `total payroll`, and `average salary`.
Key Takeaways
You now have a complete toolkit for summarizing and analyzing data:
COUNT,SUM,AVG,MIN,MAXfor basic aggregationGROUP BYto segment your dataHAVINGto filter aggregated results- Understanding of how NULLs behave in aggregates
CASE WHENfor conditional aggregation and pivots- The ability to combine all these techniques
What’s Next
So far, all your queries have worked with a single table. But real databases have data spread across multiple tables: customers in one, orders in another, products in a third. Module 3 introduces JOINs, which let you combine data from multiple tables. That’s when things get really powerful.