Monthly Salary Expense — MoM Growth & Rolling Average
Return a chained CTE salary growth summary including baseline, current, and growth metrics.
- CTEs
- Window functions
- Joins
- Subqueries
- Aggregation
Interview brief
Understand the request
Workforce strategy director The salary growth analysis chains CTEs to compute department baselines, growth rates, and cumulative trends in one report.
From salaries_history, build monthly salary expense per department (month = first 7 chars of effective_date, YYYY-MM). Then compute month-over-month growth %, a 3-month rolling average, and a has_growth flag. Return department_name, expense_month, monthly_expense, prev_month_expense, mom_growth_pct, rolling_3month_avg, has_growth — ordered by department_name, expense_month.
Return
- Return the columns shown in the expected output for this complex chain.
Constraints
- Use multiple CTEs where each builds on the previous to compute the final growth metrics.
Data you will use
Review the relevant tables before deciding how to join, filter, or aggregate them.
salaries_history
employee_idINTEGERsalaryINTEGEReffective_dateDATE
employees
employee_idINTEGERdepartment_idINTEGER
departments
department_idINTEGERdepartment_nameVARCHAR(50)
Hints, when you need them
Open one clue at a time so you still do the reasoning.
Hint 1
substr(effective_date, 1, 7) gives 'YYYY-MM' on every engine — no strftime/TO_CHAR needed.
Hint 2
NULLIF(prev, 0) guards the growth % against divide-by-zero.
Hint 3
The rolling average uses an explicit ROWS BETWEEN 2 PRECEDING AND CURRENT ROW frame (last 3 months).
Verified SQL answer
Attempt the problem first, then compare structure and reasoning—not just syntax.
Reveal solution and explanation
WITH monthly AS (SELECT e.department_id, d.department_name, substr(sh.effective_date, 1, 7) AS expense_month, SUM(sh.salary) AS monthly_expense FROM salaries_history sh INNER JOIN employees e ON sh.employee_id = e.employee_id INNER JOIN departments d ON e.department_id = d.department_id GROUP BY e.department_id, d.department_name, substr(sh.effective_date, 1, 7)) SELECT department_name, expense_month, monthly_expense, LAG(monthly_expense) OVER (PARTITION BY department_id ORDER BY expense_month) AS prev_month_expense, ROUND((monthly_expense - LAG(monthly_expense) OVER (PARTITION BY department_id ORDER BY expense_month)) * 100.0 / NULLIF(LAG(monthly_expense) OVER (PARTITION BY department_id ORDER BY expense_month), 0), 2) AS mom_growth_pct, ROUND(AVG(monthly_expense) OVER (PARTITION BY department_id ORDER BY expense_month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), 2) AS rolling_3month_avg, CASE WHEN monthly_expense > LAG(monthly_expense) OVER (PARTITION BY department_id ORDER BY expense_month) THEN 'YES' ELSE 'NO' END AS has_growth FROM monthly ORDER BY department_name, expense_month;Why this works
Month-over-month analysis is a BI staple. Bucketing the month with substr keeps it portable; LAG provides the prior period; an explicit ROWS frame gives the rolling average. NULLIF prevents the classic divide-by-zero on the first month.
Success check
The result should read like a multi-stage financial growth analysis, not a simple roster.
Expected result
Use this output to verify values, aliases, ordering, and row count.
| department_name | expense_month | monthly_expense | prev_month_expense | mom_growth_pct | rolling_3month_avg | has_growth |
|---|---|---|---|---|---|---|
| Engineering | 2020-02 | 110000 | NULL | NULL | 110000 | NO |
| Engineering | 2020-05 | 105000 | 110000 | -4.55 | 107500 | NO |
| Engineering | 2021-01 | 85000 | 105000 | -19.05 | 100000 | NO |
| Engineering | 2021-02 | 120000 | 85000 | 41.18 | 103333.33 | YES |
| Engineering | 2021-05 | 115000 | 120000 | -4.17 | 106666.67 | NO |
| Engineering | 2022-01 | 95000 | 115000 | -17.39 | 110000 | NO |
| Engineering | 2022-02 | 130000 | 95000 | 36.84 | 113333.33 | YES |
| Engineering | 2022-05 | 125000 | 130000 | -3.85 | 116666.67 | NO |
| Sales | 2020-08 | 95000 | NULL | NULL | 95000 | NO |
| Sales | 2021-06 | 68000 | 95000 | -28.42 | 81500 | NO |
Previewing 10 of 13 expected rows. Run the query in the editor to inspect the full result.
Learn the concepts behind this answer
Strengthen your understanding with these targeted learning topics: