N058-E2 Tier 5 · Expert · easy hr · Helix Systems

Return each department name, the number of active employees in it, and the average current salary across those employees

Part of Multi-CTE Query Architecture in SQL

The problem

Scenario: Helix Systems' HR director needs a current headcount and salary summary for annual workforce planning, broken out by department.

Task: Write a query to return each department name, the number of active employees in it, and the average current salary across those employees.

Assumptions:

  • An active employee has is_active equal to TRUE.
  • The current salary is the salary record whose end_date is a missing value.
  • The result covers only active employees with a current salary on record.

Output:

  • One row per department containing at least one qualifying employee.
  • Columns in this order: department_name, headcount, avg_salary.
  • Sorted by department_name ascending.
Schema · hr 4 tables
departments
id integer
name text
location text
budget numeric
salaries
id integer
employee_id integer
amount numeric
effective_date date
end_date? date
employees
id integer
name text
email text
department_id integer
manager_id? integer
hire_date date
title text
is_active boolean
job_history
id integer
employee_id integer
title text
department_id integer
start_date date
end_date? date

Run previews · Check grades

Write a query, then run it to see results here.

Worked solution Try it yourself first
Solution query
WITH
  active_employees AS (
    SELECT
      e.id AS employee_id,
      d.name AS department_name,
      s.amount AS salary
    FROM
      employees e
      JOIN departments d ON e.department_id = d.id
      JOIN salaries s ON s.employee_id = e.id
    WHERE
      e.is_active = TRUE
      AND s.end_date IS NULL
  ),
  dept_summary AS (
    SELECT
      department_name,
      COUNT(*) AS headcount,
      AVG(salary) AS avg_salary
    FROM
      active_employees
    GROUP BY
      department_name
  )
SELECT
  department_name,
  headcount,
  avg_salary
FROM
  dept_summary
ORDER BY
  department_name

The shape

Two CTEs, with the first doing all the work of identifying the right rows and the second doing all the work of summarising them. The first layer joins three tables and applies both filters, so by the time active_employees is built every row in it is a currently-paid active employee. The summary then groups that clean set by department.

Clause by clause

WITH active_employees AS (
    SELECT e.id AS employee_id, d.name AS department_name, s.amount AS salary
    FROM employees e
    JOIN departments d ON e.department_id = d.id
    JOIN salaries s ON s.employee_id = e.id
    WHERE e.is_active = TRUE
    AND s.end_date IS NULL
)

Three tables come together: employees for the identity, departments for the readable name, salaries for the amount. The two WHERE predicates run against the joined set, so only active employees with a still-open salary record survive.

dept_summary AS (
    SELECT department_name, COUNT(*) AS headcount, AVG(salary) AS avg_salary
    FROM active_employees
    GROUP BY department_name
)

GROUP BY department_name produces one row per department. COUNT(*) counts the qualifying employees in each, and AVG(salary) averages their current salaries. Engineering ends up with 16 employees averaging 164,812.50; Support with 5 averaging 100,000.

  • SELECT department_name, headcount, avg_salary FROM dept_summary ORDER BY department_name returns the summary alphabetised, ready for the workforce planning sheet.

You practiced building the per-department summary as a chain of two CTEs — one that gathers the qualifying employees with their salaries, another that summarizes them per department.

How you actually get good at SQL

Reading explains SQL. Writing it, over and over with instant feedback, is what makes you fluent.

That's the whole SQLMaxx loop: 600+ real problems, instant AI feedback, mastery you can actually see, and spaced review that won't let you forget.

A stack of SQL practice problem cards, the top card showing an employees table.
615 problems · 66 concepts

Real problems. Not toy examples.

615 hand-built problems spanning all 66 concepts, from basic SELECTs to window functions, built on real schemas and real business questions, the kind you'll actually get asked on the job. Enough reps to make SQL automatic.

A retro computer showing a SQL query marked correct with a green checkmark.
Instant AI feedback

Write a query. Know if it's right in one second.

No copying an answer and hoping it clicked. The AI grader checks your real query against real data, catches exactly what's wrong, and explains the fix in plain English, like a senior analyst reading over your shoulder on every problem.

A circular mastery progress dial filling from blue to green, the SQLMaxx diamond at its center.
Mastery tracking

Stop guessing whether you actually know it.

SQLMaxx tracks every concept and shows you what you've mastered and what's still shaky. Your skills fill in one concept at a time, so 'I think I get joins' becomes something you can prove.

A SQL query editor circled by a blue return arrow with a clock, scheduled to come back for review.
Spaced review

Learn it once. Keep it for good.

Most of what you learn this week fades by next week. So when a concept comes due for review, SQLMaxx hands you a fresh problem to solve from a blank editor, not a flashcard to re-read. A research-backed spaced-repetition algorithm (FSRS) times each return for right before you'd forget, so your SQL is still there months later, when the interview or the job actually needs it.

Practice, feedback, mastery, review. That's the loop that turns reading into real skill.

Start free

No account, no credit card. Start solving in under a minute.