Scenario: Helix Systems' HR operations team needs a current headcount of active employees broken out by department.
Task: Write a query to return each department_id and the count of active employees assigned to it.
Assumptions:
- The
employeestable holds one row per employee, withdepartment_idrecording their assignment andis_activeflagging current activity. - An active employee has
is_activeequal toTRUE. - The result covers only active employees.
Output:
- One row per department with at least one active employee.
- Columns in this order:
department_id,active_count.
Schema · hr 4 tables
Run previews · Check grades
Write a query, then run it to see results here.
Worked solution Try it yourself first
WITH
active_emps AS (
SELECT
department_id
FROM
employees
WHERE
is_active = TRUE
)
SELECT
department_id,
COUNT(*) AS active_count
FROM
active_emps
GROUP BY
department_id The shape
Two operations have to happen in order: filter to active employees, then count per department. A CTE names the filtered set first, and the main query does the per-department count on that already-narrow input.
Clause by clause
- The CTE
active_empsreadsemployeeswithWHERE is_active = TRUEand projects onlydepartment_id. The filter runs first, so every row downstream is already an active employee. SELECT department_id, COUNT(*) AS active_countreads the CTE and counts rows per group.FROM active_empsreferences the CTE by name. From the main query's perspective, it behaves exactly like a table.GROUP BY department_idproduces one row per department that has at least one active employee. Departments with zero active employees do not appear, because they had no rows inactive_empsto begin with.
Why this and not filter-in-the-main-query
Pushing the same filter into the main query — WHERE is_active = TRUE directly on employees with the GROUP BY after it — produces the same result on this data, and on PostgreSQL 12+ the planner often inlines the CTE into exactly that shape anyway. The CTE here is doing pedagogical work: the row-narrowing step is named, and the per-department count reads as a separate phase. Structurally separating the filter from the aggregation is the pattern this problem is practicing.
You practiced narrowing rows to active employees first, then counting per department — a structural shape that keeps the count over an already-trimmed set.