Second Highest Salary
Problem
A table employee(id, name, salary) holds one row per person, and several people may earn the same amount. Write a query that returns exactly one row with one column, second_highest: the second-highest distinct salary. When there is no second distinct salary, the row must still come back, holding NULL.
Examples
Input: employee = [(1, "Ana", 300), (2, "Ben", 200), (3, "Cy", 100)]
Output: [(200,)]
Why: 300 is the highest, and the highest salary below it is 200
Input: employee = [(1, "Ana", 300), (2, "Ben", 300), (3, "Cy", 200)]
Output: [(200,)]
Why: two people share 300, but salaries are compared as distinct values
Input: employee = [(1, "Ana", 300)]
Output: [(None,)]
Why: edge case, one row still comes back and it holds NULL
Hints
0 / 3
Restate the question: the second-highest salary is the highest salary among the ones that are below the top salary.
A subquery can compute the top salary once, and the outer query can then look only at the salaries under it.
Take MAX(salary) over the rows where salary < (SELECT MAX(salary) FROM employee). An aggregate over zero rows still returns one row holding NULL, which is exactly the edge case the problem asks for.
Solution
The scalar subquery (SELECT MAX(salary) FROM employee) produces one value, the top salary, and the outer WHERE keeps only the rows below it, so a repeated top salary is removed in one go. The outer MAX then picks the best of what is left. The empty case needs no special code: an aggregate without GROUP BY always returns one row, and MAX of no rows is NULL. The popular alternative, ORDER BY salary DESC with LIMIT 1 OFFSET 1 over the distinct salaries, returns no row at all when there is no second salary, so it has to be wrapped in another SELECT to turn that into NULL. The query reads the table twice, O(n), and an index on salary turns both maximums into O(log n) lookups.
import sqlite3
QUERY = """
SELECT MAX(salary) AS second_highest
FROM employee
WHERE salary < (SELECT MAX(salary) FROM employee)
"""
def run(employees):
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE employee (id INTEGER PRIMARY KEY, name TEXT, salary INTEGER)")
db.executemany("INSERT INTO employee VALUES (?, ?, ?)", employees)
return db.execute(QUERY).fetchall()
print(run([(1, "Ana", 300), (2, "Ben", 200), (3, "Cy", 100)])) # -> [(200,)]
print(run([(1, "Ana", 300), (2, "Ben", 300), (3, "Cy", 200)])) # -> [(200,)]
print(run([(1, "Ana", 300)])) # -> [(None,)]Stuck on the idea rather than the code? Subqueries covers it.