Department Budgets vs. Average
Write a PostgreSQL query to calculate the total salary budget for each department. Then, determine the average budget across all departments and identify which departments have a total budget strictly greater than this average. Display the department name, its total budget, and a flag ('Yes'/'No') indicating if it exceeds the average.
Related Concepts
Hint
This problem likely requires a multi-step approach using CTEs:
- Calculate Departmental Budgets: First, create a CTE to sum salaries for each department. Join
EmployeesandDepartments, thenGROUP BYdepartment ID and name, and useSUM(salary). - Calculate Overall Average Budget:
- Option A (Subquery/CTE): From the result of step 1, calculate the
AVG()of these departmental total budgets. This can be done in another CTE or a subquery. - Option B (Window Function): If you calculate departmental budgets, you can then use
AVG(total_department_budget) OVER ()in a subsequent step to get the overall average on each row.
- Option A (Subquery/CTE): From the result of step 1, calculate the
- Compare and Flag: Join the departmental budgets (from step 1) with the overall average budget (from step 2). Use a
CASEstatement to create the 'Yes'/'No' flag by comparing each department's total budget to the overall average.
Solution (PostgreSQL)
PostgreSQL Query (using CTEs)
-- Calculate total salary budget for each department and identify those exceeding the average
WITH DepartmentBudgets AS (
-- Step 1: Calculate total salary for each department
SELECT
d.department_id,
d.department_name,
SUM(e.salary) AS total_budget
FROM
Employees e
JOIN
Departments d ON e.department_id = d.department_id
GROUP BY
d.department_id,
d.department_name
),
AverageBudget AS (
-- Step 2: Calculate the average budget across all departments
SELECT
AVG(total_budget) AS avg_dept_budget
FROM
DepartmentBudgets
)
-- Step 3: Compare department budgets to the average and flag
SELECT
db.department_name,
db.total_budget,
CASE
WHEN db.total_budget > ab.avg_dept_budget THEN 'Yes'
ELSE 'No'
END AS exceeds_average
FROM
DepartmentBudgets db,
AverageBudget ab -- Cross join is fine here as AverageBudget has only one row
ORDER BY
db.department_name;
Explanation of the PostgreSQL Query:
DepartmentBudgets CTE:
- Purpose: Calculates the sum of salaries for each department.
SELECT d.department_id, d.department_name, SUM(e.salary) AS total_budget: Selects department identifiers and the sum of salaries.FROM Employees e JOIN Departments d ON e.department_id = d.department_id: Joins employees with their respective departments.GROUP BY d.department_id, d.department_name: Groups employees by department soSUM(e.salary)calculates the total for each one.
AverageBudget CTE:
- Purpose: Calculates the overall average of all the departmental total budgets computed in the previous CTE.
SELECT AVG(total_budget) AS avg_dept_budget FROM DepartmentBudgets: Takes all thetotal_budgetvalues from theDepartmentBudgetsCTE and calculates their average. This CTE will produce a single row with a single column (avg_dept_budget).
SELECT Statement:
- Purpose: Joins the individual department budgets with the overall average budget and determines if each department exceeds this average.
SELECT db.department_name, db.total_budget, ...: Selects the required output columns.FROM DepartmentBudgets db, AverageBudget ab: This performs aCROSS JOIN(implicitly, since there's no ON clause andAverageBudgethas one row). This effectively makes the singleavg_dept_budgetvalue available to compare against each department's budget fromdb.CASE WHEN db.total_budget > ab.avg_dept_budget THEN 'Yes' ELSE 'No' END AS exceeds_average: This creates the flag. If a department'stotal_budgetis strictly greater than theavg_dept_budget, it's flagged 'Yes'; otherwise, 'No'.ORDER BY db.department_name: Orders the final result alphabetically by department name for consistent output.
Alternative with Window Function for Average: Instead of a separate AverageBudget CTE, you could calculate the average using a window function directly after calculating department budgets if you prefer fewer CTEs (though the CTE approach is often clearer for multi-step calculations).
WITH DepartmentBudgets AS (
SELECT
d.department_id,
d.department_name,
SUM(e.salary) AS total_budget
FROM Employees e
JOIN Departments d ON e.department_id = d.department_id
GROUP BY d.department_id, d.department_name
)
SELECT
department_name,
total_budget,
CASE
WHEN total_budget > AVG(total_budget) OVER () THEN 'Yes'
ELSE 'No'
END AS exceeds_average
FROM DepartmentBudgets
ORDER BY department_name;
In this alternative, AVG(total_budget) OVER () calculates the average of total_budget across all rows produced by the DepartmentBudgets CTE, making it available on each row for direct comparison.
Your Turn! What are your thoughts on this problem or alternative ways to solve it? Share your own SQL query attempts or insights in the comments below!