Interview Question of the Week #050 – Query to Retrieve Second Highest Salary of Employee

Question: How do I retrieve the second, or Nth, highest employee salary? First decide whether you mean the Nth distinct salary or the Nth employee after sorting. The original query answers the distinct-salary question and returns every employee tied at that level.

Stone stacks have three distinct heights, with two middle-height stacks and one marked by a vermilion thread

Some interview questions never seem to get old. I keep seeing this one when helping hire developers and DBAs. A candidate who asks what to do with equal salaries is already asking the right question.

DECLARE @Employee TABLE(EmployeeID int,Salary decimal(12,2));
INSERT @Employee VALUES(1,90000),(2,75000),(3,75000),(4,60000),(5,NULL);
DECLARE @N int=2;
-- Original correlated approach: count distinct salaries above this salary.
SELECT E1.EmployeeID,E1.Salary
FROM @Employee AS E1
WHERE E1.Salary IS NOT NULL
AND @N-1=(SELECT COUNT(DISTINCT E2.Salary)
          FROM @Employee AS E2 WHERE E2.Salary>E1.Salary)
ORDER BY E1.EmployeeID;

-- Equivalent distinct-salary ranking, retaining employees tied at that rank.
WITH Ranked AS
(
 SELECT EmployeeID,Salary,DENSE_RANK() OVER(ORDER BY Salary DESC) AS SalaryRank
 FROM @Employee WHERE Salary IS NOT NULL
)
SELECT EmployeeID,Salary FROM Ranked
WHERE SalaryRank=@N ORDER BY EmployeeID;

Both queries return employees 2 and 3, each earning 75000.00. There is one distinct salary above them: 90000. Ties do not consume additional salary levels.

Native SQL Server 2025 results from both queries retain employees 2 and 3, each tied at the second distinct salary of 75000.
Native SQL Server 2025 results from both queries retain employees 2 and 3, each tied at the second distinct salary of 75000.

Finding the second highest salary with a correlated query

For a candidate amount, count the distinct higher salaries. Exactly N minus one higher levels means this is the Nth level. The reference to E1.Salary makes the subquery logically correlated; it does not require SQL Server physically to execute a separate subquery once per employee. Read the plan rather than assuming that implementation.

The NULL filter matters. With N=1, the original unfiltered query could also admit employees whose salary was unknown because no comparison is true for their NULL salary. An unknown value is not the highest known salary.

Ranking makes the tie policy visible

DENSE_RANK gives equal amounts the same rank without gaps. ROW_NUMBER instead chooses employee positions and needs a deterministic tie-breaker such as EmployeeID. RANK leaves gaps after ties, so its third rank is not necessarily the third distinct salary.

Use a positive N. When the table has fewer distinct known amounts than requested, these queries return no rows. Neither spelling is automatically faster for every table; data distribution, indexing and the plan decide the work.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

Previous Post
Interview Question of the Week #049 – Taking Database Offline
Next Post
Interview Question of the Week #051 – Actual Execution Plan vs. Estimated Execution Plan

Related Posts

No results found.

10 Comments. Leave new

  • Do You think that ranking (dense_rank) could be a better way for similar queries in terms of clarity and execution cost ? Like:

    WITH SalaryRanking_CTE AS
    (
    SELECT *,
    DENSE_RANK() OVER (ORDER BY Salary DESC) as Ranking
    FROM Salary
    )
    SELECT *
    FROM SalaryRanking_CTE
    WHERE Ranking = n

    Reply
  • Hi Pinal, I’ve tried the script on SQL2012 and it does not recognized N in the Where clause. The error reads: Msg 207, Level 16, State 1, Line 4
    Invalid column name ‘N’.
    Do I miss something here? Thank you!

    Reply
    • N is a variable.. declare @N int =2 .. then replace N with @N and this give you the second highest

      Reply
    • Yvonne, if you’d like to run his query, replace “N” with either a constant (e.g. “2” to get the second highest salary) or declare a variable “@N” and set it to “2” (or whatever number you’d like).

      Reply
  • Richard Armstrong-Finnerty
    December 27, 2015 6:26 pm

    — Using ROW_NUMBER()
    SELECT *
    FROM
    (
    SELECT ROW_NUMBER() OVER(ORDER BY Salary DESC) as rownum ,*
    FROM Employee
    ) AS Employee
    WHERE rownum = 2

    Reply
  • abdulhannanijaz
    December 28, 2015 7:06 pm

    WE can also use Offset and Fetch to get 2nd highest value
    –Sample For Adventure works 2012

    SELECT *
    FROM HumanResources.EmployeePayHistory
    ORDER BY Rate DESC
    OFFSET 1 ROW FETCH NEXT 1 ROW ONLY

    Reply
  • abdulhannanijaz
    December 28, 2015 7:08 pm

    Offset can be used now to get value of any level
    –For adventure works 2012
    SELECT *
    FROM HumanResources.EmployeePayHistory
    ORDER BY Rate DESC
    OFFSET 1 ROW FETCH NEXT 1 ROW ONLY

    Reply
  • Simple query: select top 1 * from(select top 2 * from employee order by salary desc) order by salary asc

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.