Back to Practical SQL Queries
Q9Question 9 of 34

Max Salary per City, Total Salary per Department, and Second-Highest Salary

Key Concepts
  • MAX grouped by city returns the highest salary in each city group using GROUP BY city.
  • SUM grouped by department returns the payroll cost per department for budgeting.
  • DENSE_RANK() OVER (ORDER BY salary DESC) filtered to rank 2 finds the second-highest distinct salary.
  • DENSE_RANK does not skip ranks on ties, unlike RANK which skips (1,1,3 vs 1,1,2).
  • The LIMIT 1 OFFSET 1 approach is wrong when the top salary is held by multiple employees.
  • To get the employee name with MAX per city, use ROW_NUMBER with PARTITION BY city.
  • Money columns must always be DECIMAL or NUMERIC, never FLOAT, for financial accuracy.

Definition

`MAX` grouped by city, `SUM` grouped by department, and `DENSE_RANK() = 2` (or a nested max-below-max subquery) for the second-highest salary.
  • MAX per city – SELECT city, MAX(salary) FROM employee GROUP BY city returns the highest salary in each city group.
  • SUM per department – SELECT department, SUM(salary) FROM employee GROUP BY department returns the payroll cost for each department.
  • Second-highest salary – use DENSE_RANK() OVER (ORDER BY salary DESC) filtered to rank 2, which correctly handles ties by not skipping rank numbers.
  • Subquery alternative – SELECT MAX(salary) FROM employee WHERE salary < (SELECT MAX(salary) FROM employee) finds the second-highest distinct value without window functions.
  • Name retrieval – to get the employee name alongside MAX(salary) per city, use ROW_NUMBER() OVER (PARTITION BY city ORDER BY salary DESC) and keep row 1.

At the National Bank of India, HR has a sheet listing every employee with salary, city, and department. For the first question, sort by city and pick the top salary in each city pile. For the second question, sort by department and add up each pile. For the third question, line everyone up by salary and find the person just below the top earner.

Understanding the Concepts

What does "max salary per city" mean?

This is a grouped aggregate query. You partition the employee rows by city and apply MAX(salary) to each group. The result contains one row per city with the highest salary found in that city.

SQL
-- Max salary per city at National Bank of India
SELECT city, MAX(salary) AS max_salary
FROM employee
GROUP BY city
ORDER BY max_salary DESC;
TEXT
city       | max_salary
-----------+-----------
Mumbai     | 185000.00
Delhi      | 172000.00
Bangalore  | 168000.00

If you also need the name of the person earning that max salary, a plain GROUP BY cannot give it because name is not aggregated. You need a window function approach.

How do you get the employee name alongside the max salary per city?

Use ROW_NUMBER() partitioned by city and ordered by salary descending. Keep only the rows where the row number equals one.

SQL
SELECT city, name, salary
FROM (
    SELECT city, name, salary,
           ROW_NUMBER() OVER (
               PARTITION BY city ORDER BY salary DESC, id DESC
           ) AS rn
    FROM employee
) ranked
WHERE rn = 1;

The id DESC tie-breaker ensures deterministic results when two employees in the same city share the top salary.

What does "total salary per department" mean?

This is another grouped aggregate. You partition by department and apply SUM(salary) to compute the payroll cost for each department.

SQL
-- Total salary cost per department
SELECT department, SUM(salary) AS total_salary, COUNT(*) AS headcount
FROM employee
GROUP BY department
ORDER BY total_salary DESC;
TEXT
department   | total_salary | headcount
-------------+--------------+----------
Engineering  | 4250000.00   | 25
Operations   | 3120000.00   | 30
Compliance   | 1850000.00   | 12

Rajesh uses this for the quarterly budget review at National Bank of India.

How do you find the second-highest salary using a window function?

DENSE_RANK() assigns rank 1 to the highest salary and rank 2 to the next distinct salary, even when multiple employees share the top value.

SQL
SELECT name, salary
FROM (
    SELECT name, salary,
           DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
    FROM employee
) t
WHERE rnk = 2;
TEXT
name    | salary
--------+-----------
Priya   | 172000.00
Ankit   | 172000.00

Both Priya and Ankit share the second-highest salary, and DENSE_RANK returns them both because it does not skip ranks on ties.

How do you find the second-highest salary without window functions?

Use a subquery that finds the maximum salary strictly below the overall maximum.

SQL
SELECT MAX(salary) AS second_highest
FROM employee
WHERE salary < (SELECT MAX(salary) FROM employee);

This returns just the value, not the employee name. It handles duplicates of the top salary correctly and returns NULL if there is no second distinct salary.

Why is LIMIT 1 OFFSET 1 wrong for second-highest salary?

A common mistake is to use ORDER BY salary DESC LIMIT 1 OFFSET 1. This fails when the top salary is held by more than one employee, because the second row is just another person at the same top salary.

SQL
-- Wrong approach when top salary is duplicated
SELECT salary FROM employee ORDER BY salary DESC LIMIT 1 OFFSET 1;
-- If top two rows are both 185000, this returns 185000 again
SQL
-- Correct approach: use DISTINCT first
SELECT DISTINCT salary FROM employee ORDER BY salary DESC LIMIT 1 OFFSET 1;

The DISTINCT version works but DENSE_RANK is clearer and also gives you the employee names.

What is the difference between DENSE_RANK and RANK for this problem?

| Feature | RANK() | DENSE_RANK() |
|---|---|---|
| Ties | Assigns same rank, then skips (1,1,3) | Assigns same rank, no skip (1,1,2) |
| Second-highest | WHERE rnk = 2 returns nothing if two share rank 1 | WHERE rnk = 2 always returns the next distinct salary |
| Use case | "Who finished third in a race" (position matters) | "What is the second-highest distinct value" |

For "second-highest salary" always use DENSE_RANK because it does not skip rank numbers after ties.

How do you find the second-highest salary per department?

Add PARTITION BY department to the window function so ranking restarts for each department.

SQL
SELECT department, name, salary
FROM (
    SELECT department, name, salary,
           DENSE_RANK() OVER (
               PARTITION BY department ORDER BY salary DESC
           ) AS rnk
    FROM employee
) t
WHERE rnk = 2;

This returns the second-highest earner in each department, which Pankaj uses for internal pay-band analysis at National Bank of India.

How do you implement this in Java with JDBC?

Rajesh writes a utility to pull the max salary per city from the bank's employee database.

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.sql.*;

public class SalaryReportService {

    public void printMaxSalaryPerCity(Connection conn) throws SQLException {
        String sql = "SELECT city, MAX(salary) AS max_salary "
                   + "FROM employee GROUP BY city ORDER BY max_salary DESC";
        try (PreparedStatement ps = conn.prepareStatement(sql);
             ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                String city = rs.getString("city");
                BigDecimal maxSalary = rs.getBigDecimal("max_salary");
                System.out.println(city + ": " + maxSalary);
            }
        }
    }

    public BigDecimal getSecondHighestSalary(Connection conn) throws SQLException {
        String sql = "SELECT MAX(salary) AS second_highest "
                   + "FROM employee "
                   + "WHERE salary < (SELECT MAX(salary) FROM employee)";
        try (PreparedStatement ps = conn.prepareStatement(sql);
             ResultSet rs = ps.executeQuery()) {
            if (rs.next()) {
                return rs.getBigDecimal("second_highest");
            }
            return null;
        }
    }
}

What are the performance considerations?

For MAX and SUM aggregates, an index on (city, salary) or (department, salary) helps the engine avoid a full table scan when the table is large. For the DENSE_RANK query, an index on salary DESC speeds up the sort. For per-department ranking, a composite index on (department, salary DESC) is optimal.

SQL
-- Indexes for National Bank of India employee reports
CREATE INDEX idx_emp_city_salary ON employee (city, salary DESC);
CREATE INDEX idx_emp_dept_salary ON employee (department, salary DESC);

Money columns must always be DECIMAL or NUMERIC, never FLOAT or DOUBLE, to avoid rounding errors in financial calculations.

What are common interview follow-up questions?

  • How do you find the Nth highest salary (change WHERE rnk = 2 to WHERE rnk = N).
  • How do you find the top 3 earners per department (change WHERE rnk = 2 to WHERE rnk <= 3).
  • How do you handle NULL salaries (NULL values are ignored by MAX and SUM but could affect DENSE_RANK ordering).
  • How do you get the median salary (use PERCENTILE_CONT(0.5) or a ROW_NUMBER approach).

Visual Overview

DIAGRAM
┌─────────────────────────────────────────────────────┐
│              employee table                          │
│  ID | Name   | Salary   | City      | Department    │
│  1  | Rajesh | 185000   | Mumbai    | Engineering   │
│  2  | Priya  | 172000   | Delhi     | Operations    │
│  3  | Ankit  | 172000   | Mumbai    | Compliance    │
│  4  | Meera  | 168000   | Bangalore | Engineering   │
└─────────────────────────────────────────────────────┘
          │                    │                  │
          ▼                    ▼                  ▼
  ┌───────────────┐  ┌──────────────┐  ┌──────────────────┐
  │ MAX per City  │  │ SUM per Dept │  │ DENSE_RANK = 2   │
  │ GROUP BY city │  │ GROUP BY dept│  │ ORDER BY sal DESC│
  └───────┬───────┘  └──────┬───────┘  └────────┬─────────┘
          ▼                  ▼                   ▼
  Mumbai: 185000     Eng: 353000        Priya: 172000
  Delhi:  172000     Ops: 172000        Ankit: 172000
  Bangalore: 168000  Comp: 172000

Key Takeaway

This question combines three fundamental SQL patterns. For the maximum salary per city, group by city and take MAX. If you also need the person's name, switch to ROW_NUMBER() OVER (PARTITION BY city ORDER BY salary DESC) and keep row one, because a plain GROUP BY cannot return the non-aggregated name column.

For the total salary per department, group by department and take SUM. This gives the payroll cost that Rajesh uses for quarterly budgeting at National Bank of India.

For the second-highest salary, the cleanest approach is DENSE_RANK() OVER (ORDER BY salary DESC) filtered to rank two. DENSE_RANK is the correct choice over RANK because it does not skip ranks on ties, so it always returns the next distinct salary value. A pure-SQL alternative without window functions is SELECT MAX(salary) FROM employee WHERE salary < (SELECT MAX(salary) FROM employee). The LIMIT 1 OFFSET 1 approach is a common mistake because it breaks when the top salary is shared by multiple employees.

The senior additions are that DENSE_RANK versus RANK matters, that per-department second-highest adds PARTITION BY department to the window, that salary columns must be DECIMAL or NUMERIC for financial accuracy, and that proper indexes on (city, salary) and (department, salary) keep these aggregates fast on large tables.

Remember one sentence: MAX grouped by city, SUM grouped by department, and DENSE_RANK equal to two for the second-highest salary.

---

Share
Use ← → arrow keys to navigate