CoreTrail

ROW_NUMBER, RANK & DENSE_RANK

Control ties and select the top rows or distinct values.

CorePostgreSQL2 min read
On this page

For salaries 120, 100, 100, 80 sorted descending:

Salary ROW_NUMBER RANK DENSE_RANK
120 1 1 1
100 2 2 2
100 3 2 2
80 4 4 3

ROW_NUMBER assigns different positions to tied rows; which tied row comes first needs a tie-breaker. RANK leaves gaps after ties. DENSE_RANK numbers distinct ordering values without gaps.

Three highest distinct salaries in every department

WITH ranked AS (
    SELECT employee_id, department_id, salary,
           DENSE_RANK() OVER (
               PARTITION BY department_id
               ORDER BY salary DESC
           ) AS salary_rank
    FROM employees
    WHERE salary IS NOT NULL
)
SELECT employee_id, department_id, salary
FROM ranked
WHERE salary_rank <= 3;

This can return more than three employees per department. For at most three employees, use ROW_NUMBER() and a tie-breaker such as employee_id.

Do not add employee_id to the DENSE_RANK ordering when equal salaries should tie. It would rank the salary/ID combination instead.

Second-highest distinct salary, returning NULL if absent

SELECT MAX(salary) AS second_highest_salary
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);

The aggregate returns one row even if no qualifying salary exists. An alternative using DENSE_RANK() = 2 naturally returns every employee at that salary, which is a different output shape.

References

Type a concept, keyword, or function.