This lesson on The Nth Highest Salary Problem is hands-on and example-driven. You will master five distinct SQL approaches to retrieve the Nth highest salary from an employee table, handling duplicate salaries and null values cleanly. You can apply these patterns in technical interviews to demonstrate both standard ANSI SQL knowledge and database-specific optimizations.
What You'll Be Able To Do
- Write nested subqueries with MAX to find top-tier ordinal salaries.
- Implement LIMIT with OFFSET parameters alongside DISTINCT to eliminate duplicate rankings.
- Construct correlated self-joins comparing row values to find relative rank.
- Utilize DENSE_RANK() window functions inside derived tables to partition salary tiers.
- Apply NOT IN and inequality operators for quick second-highest salary lookups.
Detailed Concept Walkthrough
1. Nested Aggregate MAX Approach
Finds the Nth highest salary by filtering out values equal to or greater than the maximums retrieved by nested inner subqueries.
- Mechanism: An innermost subquery extracts the absolute MAX salary, while outer queries successively fetch the MAX salary that is strictly less than the subquery's result.
- Under the Hood: Each nesting level adds another full table scan or index scan to resolve the scalar aggregate before filtering the next level up.
- Best Practice: Use this method primarily for the 2nd highest salary; scaling beyond 3 levels causes deep query nesting that degrades maintainability and performance.
-- 2nd highest salary using nested MAX
SELECT MAX(salary)
FROM employee_info
WHERE salary < (
SELECT MAX(salary)
FROM employee_info
);
Key Takeaway: Nested MAX queries work well for the 2nd highest value but become unmaintainable as N grows.
2. LIMIT and OFFSET with DISTINCT
Sorts distinct salaries in descending order and skips N-1 records to pinpoint the target value.
- Mechanism: The query applies ORDER BY salary DESC with a LIMIT offset, count syntax (or LIMIT count OFFSET offset) to extract exactly one record after skipping N-1 rows.
- Under the Hood: Without DISTINCT, identical salary values occupy multiple offset slots, causing the query to return duplicates instead of the true Nth highest tier.
- Syntax Rule: The syntax LIMIT n-1, 1 skips n-1 records and retrieves the single next row representing the Nth distinct salary.
-- Nth highest salary using LIMIT (e.g., 3rd highest: LIMIT 2, 1)
SELECT DISTINCT salary
FROM employee_info
ORDER BY salary DESC
LIMIT 2, 1;
Key Takeaway: Always include DISTINCT before ORDER BY and LIMIT to prevent duplicate values from consuming ordinal positions.
3. Correlated Self-Join Approach
Identifies a salary by counting how many distinct salaries in the same table are strictly greater than it.
- Mechanism: An outer table alias e1 is compared against an inner alias e2, where the inner correlated subquery calculates COUNT(DISTINCT e2.salary) WHERE e2.salary > e1.salary.
- Execution Flow: For the Nth highest salary, there must be exactly N-1 higher distinct values in the table; matching this condition filters the exact row.
- Best Practice: This query uses standard ANSI SQL without proprietary pagination clauses, making it universally portable across all RDBMS engines.
-- 3rd highest salary using correlated subquery (N - 1 = 2)
SELECT e1.salary
FROM employee_info e1
WHERE 2 = (
SELECT COUNT(DISTINCT e2.salary)
FROM employee_info e2
WHERE e2.salary > e1.salary
);
Key Takeaway: Correlated subqueries evaluate the count of strictly higher distinct salaries to match N-1 without dialect-specific pagination.
4. Window Functions with DENSE_RANK
Assigns contiguous rank integers to unique salary values in descending order and filters by rank in an outer query.
- Mechanism: DENSE_RANK() OVER (ORDER BY salary DESC) assigns identical ranks to duplicate salaries without skipping subsequent ranks.
- Under the Hood: Because window functions are evaluated after the WHERE clause, the ranking logic must execute inside an inline view or derived table.
- Best Practice: Use DENSE_RANK instead of RANK or ROW_NUMBER to ensure tied salaries share a tier and the next distinct salary receives the consecutive integer.
-- Nth highest salary using DENSE_RANK (e.g., 3rd highest)
SELECT name, salary
FROM (
SELECT name, salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS salary_rank
FROM employee_info
) AS temp
WHERE salary_rank = 3;
Key Takeaway: DENSE_RANK guarantees consecutive integer rankings across duplicate values, making it the industry standard approach.
5. Filtering with NOT IN and Inequality
Excludes the top salary using NOT IN or != to find the next maximum value in a single step.
- Mechanism: The subquery finds MAX(salary), and the outer query takes MAX(salary) WHERE salary NOT IN (subquery) or WHERE salary != (subquery).
- Under the Hood: This functions identically to the nested MAX approach but uses membership/inequality operators instead of the < relational operator.
- Nuance: This pattern is strictly limited to finding the 2nd highest value and does not generalize cleanly to arbitrary N.
-- 2nd highest salary using NOT IN
SELECT MAX(salary)
FROM employee_info
WHERE salary NOT IN (
SELECT MAX(salary)
FROM employee_info
);
Key Takeaway: NOT IN and != provide a direct syntax alternative for the 2nd highest salary but fail to scale to Nth ranks.
Topics Covered in The Nth Highest Salary Problem
- Problem Overview (0:00 - 0:50) — Introduces the problem of finding the Nth highest salary and outlines solving it via five methods.
- Nested MAX Approach (0:50 - 2:10) — Demonstrates finding the 2nd and 3rd highest salaries using nested MAX subqueries.
- LIMIT Clause and DISTINCT (2:10 - 4:15) — Explains LIMIT offset syntax and why DISTINCT is necessary to handle duplicate salary rows.
- Correlated Self-Join (4:15 - 6:20) — Shows how to count strictly greater distinct salaries using correlated subqueries matching N-1.
- DENSE_RANK Window Function (6:20 - 7:50) — Illustrates ranking salaries with DENSE_RANK() inside a derived table to retrieve rank N.
- NOT IN and Inequality Filter (7:50 - 8:35) — Covers finding the second highest salary using MAX combined with NOT IN or not equal operators.
SQL Cheat Sheet
-
MAX() with Subquery— Fetches max salary below the inner subquery maximumSELECT MAX(salary) FROM employee_info WHERE salary < (SELECT MAX(salary) FROM employee_info); -
LIMIT offset, count— Skips offset rows and returns specified row countSELECT DISTINCT salary FROM employee_info ORDER BY salary DESC LIMIT 1, 1; -
Correlated COUNT(DISTINCT)— Counts rows with higher salaries per candidate rowSELECT salary FROM employee_info e1 WHERE (N-1) = (SELECT COUNT(DISTINCT e2.salary) FROM employee_info e2 WHERE e2.salary > e1.salary); -
DENSE_RANK() OVER()— Assigns consecutive rank numbers without gaps for tiesSELECT salary FROM (SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) rnk FROM employee_info) t WHERE rnk = 3; -
NOT IN / != with MAX()— Selects maximum salary excluding the overall maximumSELECT MAX(salary) FROM employee_info WHERE salary != (SELECT MAX(salary) FROM employee_info);
Comparison Table
| Approach | Scalability to N | Handles Duplicates |
|---|---|---|
| Nested MAX | Poor (requires N nested queries) | Yes (via aggregate MAX) |
| LIMIT / OFFSET | High (change offset to N-1) | Requires explicit DISTINCT |
| Correlated Subquery | High (parameterize N-1 count) | Requires COUNT(DISTINCT) |
| DENSE_RANK() | High (filter rank = N) | Native (handles ties seamlessly) |
| NOT IN / != | None (2nd highest only) | Yes (via aggregate MAX) |
Common Pitfalls
- Mistake: Using LIMIT N-1, 1 without DISTINCT on tables with duplicate values. Avoid: Always apply DISTINCT before ORDER BY when using LIMIT for ordinal ranking.
- Mistake: Using RANK() instead of DENSE_RANK() when duplicate salaries exist. Avoid: Use DENSE_RANK() to prevent duplicate values from skipping subsequent rank integers.
- Mistake: Filtering window function results directly in the WHERE clause of the same query block. Avoid: Wrap the window function in a derived table or CTE before filtering rank.
- Mistake: Relying on nested MAX or NOT IN queries for dynamic Nth rank calculations. Avoid: Use DENSE_RANK() or correlated subqueries for generalizable Nth rank queries.
FAQs
- Why does LIMIT 2, 1 return the wrong result without DISTINCT? Without DISTINCT, duplicate salaries take up row offsets, causing LIMIT to fetch another instance of the 2nd highest salary instead of the true 3rd highest.
- What is the difference between RANK() and DENSE_RANK() for this problem? RANK() skips subsequent rank numbers after ties (e.g., 1, 2, 2, 4), whereas DENSE_RANK() maintains consecutive numbers (e.g., 1, 2, 2, 3).
- Which approach is most preferred in production and interviews? DENSE_RANK() is preferred because it handles duplicates cleanly, scales to any N, and complies with modern SQL standards.