Department Hierarchy and Salary Analysis
For every employee, show their manager's name, department, their salary gap vs their manager, their salary rank within their department, and the department average salary.
- CTEs
- Window functions
- Joins
- Subqueries
- Aggregation
Challenge brief
Understand the request
People Analytics is running a compensation benchmarking exercise and needs a full view of salary hierarchy, manager comparisons, and department standings.
Build a salary hierarchy report with manager comparison, department rank, and department average using a CTE and self-join.
Return
- employee_name
- manager_name (NULL for top-level)
- department_name
- employee_salary
- manager_salary (NULL for top-level)
- salary_difference (manager salary minus employee salary, 0 if no manager)
- dept_salary_rank (rank within department, 1 = highest)
- dept_avg_salary
Constraints
- Include every employee, including top-level employees with no manager
- Salary rank resets within each department and preserves ties
- Report a zero manager salary difference when no manager exists
- Return departments alphabetically and employees by department salary rank
Data you will use
Review the relevant tables before deciding how to join, filter, or aggregate them.
employees
employee_idINTEGERfirst_nameVARCHAR(50)last_nameVARCHAR(50)salaryINTEGERmanager_idINTEGERdepartment_idINTEGER
departments
department_idINTEGERdepartment_nameVARCHAR(100)
Hints, when you need them
Open one clue at a time so you still do the reasoning.
Hint 1
This query combines 3 techniques: (1) a CTE for department averages, (2) a self-join to look up the manager's name and salary from the same employees table, (3) RANK() OVER (PARTITION BY ...) to rank within each department. Build each piece separately before combining.
Hint 2
CTE: SELECT department_id, ROUND(AVG(salary),2) AS dept_avg_salary FROM employees GROUP BY department_id. Main query: alias employees twice — e for the employee, m for the manager. LEFT JOIN e to m ON e.manager_id = m.employee_id. RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC).
Hint 3
Scaffold: compute department averages first, self-join employees to managers without dropping leaders, then add a department-partitioned salary rank.
Verified SQL answer
Attempt the problem first, then compare structure and reasoning—not just syntax.
Reveal solution and explanation
WITH dept_stats AS (SELECT department_id, ROUND(AVG(salary), 2) AS dept_avg_salary FROM employees GROUP BY department_id) SELECT e.first_name || ' ' || e.last_name AS employee_name, m.first_name || ' ' || m.last_name AS manager_name, d.department_name, e.salary AS employee_salary, m.salary AS manager_salary, COALESCE(m.salary - e.salary, 0) AS salary_difference, RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS dept_salary_rank, ds.dept_avg_salary FROM employees e LEFT JOIN employees m ON e.manager_id = m.employee_id INNER JOIN departments d ON e.department_id = d.department_id INNER JOIN dept_stats ds ON e.department_id = ds.department_id ORDER BY d.department_name, dept_salary_rank;Why this works
PARTITION BY department_id in the window function resets the rank counter for each department — so both Engineering and Operations have a rank 1. COALESCE handles top-level employees (Jeff Bezos) who have no manager: m.salary is NULL, so COALESCE returns 0. LEFT JOIN keeps Jeff even though he has no manager row.
Success check
10 rows — all employees with hierarchy context, sorted by department then rank
Expected result
Use this output to verify values, aliases, ordering, and row count.
| employee_name | manager_name | department_name | employee_salary | manager_salary | salary_difference | dept_salary_rank | dept_avg_salary |
|---|---|---|---|---|---|---|---|
| Andy Jassy | Jeff Bezos | Engineering | 180000 | 200000 | 20000 | 1 | 132500 |
| Lisa Wang | Andy Jassy | Engineering | 135000 | 180000 | 45000 | 2 | 132500 |
| John Smith | Andy Jassy | Engineering | 120000 | 180000 | 60000 | 3 | 132500 |
| David Lee | Andy Jassy | Engineering | 95000 | 180000 | 85000 | 4 | 132500 |
| Jeff Bezos | NULL | Executive | 200000 | NULL | 0 | 1 | 200000 |
| Sarah Connor | Andy Jassy | Operations | 85000 | 180000 | 95000 | 1 | 53200 |
| Rachel Green | Sarah Connor | Operations | 48000 | 85000 | 37000 | 2 | 53200 |
| Tom Wilson | Sarah Connor | Operations | 46000 | 85000 | 39000 | 3 | 53200 |
| Emily Davis | Sarah Connor | Operations | 45000 | 85000 | 40000 | 4 | 53200 |
| Michael Brown | Sarah Connor | Operations | 42000 | 85000 | 43000 | 5 | 53200 |
Learn the concepts behind this answer
Strengthen your understanding with these targeted learning topics:
Continue practicing
Explore related company challenges
Airbnb
Independent Airbnb-style marketplace, booking, listing, payment, review, and guest analytics SQL practice.
Uber
Independent Uber-style mobility marketplace SQL practice covering trips, drivers, riders, pricing, payments, and promotions.
Microsoft
Independent Microsoft-style cloud, productivity, subscription, usage, support, and customer analytics SQL practice.
Return to the complete interview preparation experience.