Lição 12.4 · Tempo de leitura: ~7 min
"Encontre o segundo maior salário" é uma das perguntas mais frequentes em entrevistas de SQL, normalmente seguida de "agora o N-ésimo" e "agora em cada departamento". Nesta lição, você vai resolvê-la de cinco formas: MAX com uma subconsulta, LIMIT ... OFFSET, DENSE_RANK() e uma subconsulta correlacionada, e vai ver as armadilhas que os entrevistadores procuram: salários repetidos, N-ésimo valor inexistente e grupos.
N-ésimo Maior Salário em SQL
A tarefa parece trivial, e é exatamente por isso que é popular: uma primeira tentativa costuma funcionar nos dados de exemplo e falhar num caso-limite. Uma boa resposta trata três coisas: empates (dois funcionários com o mesmo salário), ausência de resposta (menos de N salários diferentes) e grupos (o N-ésimo salário de cada departamento).
Os exemplos usam a sintaxe do MySQL 8.0 e criam a sua própria tabela, por isso execute-os no playground num banco sem dados de exemplo.
Dados de Exemplo
CREATE TABLE employee (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
department VARCHAR(30) NOT NULL,
salary INT NOT NULL
);
INSERT INTO employee VALUES
(1, 'Alice', 'Sales', 5000),
(2, 'Bob', 'Sales', 7000),
(3, 'Carol', 'Sales', 7000),
(4, 'Dave', 'IT', 9000),
(5, 'Eve', 'IT', 6500),
(6, 'Frank', 'IT', 9000),
(7, 'Grace', 'HR', 4800),
(8, 'Heidi', 'HR', 5200);
Os salários distintos, do maior para o menor, são 9000, 7000, 6500, 5200, 5000, 4800. Duas pessoas ganham 9000 e duas ganham 7000, por isso o segundo maior salário é 7000, e não 9000.
Solução 1: o MAX Abaixo do MAX
O segundo maior salário é o maior salário que é menor do que o máximo:
SELECT MAX(salary) AS second_highest_salary
FROM employee
WHERE salary < (SELECT MAX(salary) FROM employee);
Resultado: 7000.
É curto, funciona em qualquer banco de dados, trata empates e devolve NULL quando não há segundo salário, porque o MAX de um conjunto vazio é NULL. O seu limite: só funciona para N = 2. Para o terceiro salário seria preciso mais um nível de aninhamento.
Solução 2: DISTINCT com LIMIT e OFFSET
Ordene os salários do maior para o menor e salte N − 1 deles:
SELECT DISTINCT salary
FROM employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1;
Resultado: 7000.
O DISTINCT é essencial. Sem ele, a lista ordenada é 9000, 9000, 7000, … e o OFFSET 1 cai no segundo 9000:
-- Errado: devolve outra vez o maior salário por causa do empate
SELECT salary
FROM employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1;
Resultado: 9000, o maior salário, e não o segundo maior.
Devolver NULL Quando Não Há Resposta
Se N for maior do que o número de salários distintos, a consulta devolve nenhuma linha. As tarefas de entrevista costumam exigir um NULL nesse caso. Envolva a consulta numa subconsulta escalar: uma subconsulta que não encontra nada devolve NULL.
SELECT (
SELECT DISTINCT salary
FROM employee
ORDER BY salary DESC
LIMIT 1 OFFSET 9
) AS tenth_highest_salary;
Resultado: uma linha com NULL; há apenas 6 salários distintos.
LIMIT ... OFFSET é sintaxe do MySQL, PostgreSQL e SQLite. O SQL Server e o Oracle 12c+ usam OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY.
Solução 3: DENSE_RANK
Uma função de janela classifica cada linha, e a consulta externa escolhe a posição pretendida. É a solução mais flexível e a que os entrevistadores normalmente esperam:
WITH ranked AS (
SELECT name, salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS salary_rank
FROM employee
)
SELECT name, salary
FROM ranked
WHERE salary_rank = 2;
Resultado: Bob e Carol, ambos com 7000.
Também responde à pergunta seguinte, "quem ganha o segundo maior salário". Troque 2 por qualquer N. Só o DENSE_RANK está correto aqui:
| salary | ROW_NUMBER | RANK | DENSE_RANK |
|---|---|---|---|
| 9000 | 1 | 1 | 1 |
| 9000 | 2 | 1 | 1 |
| 7000 | 3 | 3 | 2 |
| 7000 | 4 | 3 | 2 |
| 6500 | 5 | 5 | 3 |
O ROW_NUMBER dá números diferentes a salários iguais, e o RANK deixa lacunas depois dos empates, por isso RANK = 2 não encontra ninguém. O DENSE_RANK numera os valores distintos sem lacunas.
Solução 4: Subconsulta Correlacionada
Antes das funções de janela, a resposta clássica era: um salário é o N-ésimo maior se exatamente N salários distintos forem maiores ou iguais a ele.
SELECT DISTINCT e1.salary
FROM employee e1
WHERE 3 = (
SELECT COUNT(DISTINCT e2.salary)
FROM employee e2
WHERE e2.salary >= e1.salary
);
Resultado: 6500, o terceiro maior salário.
Funciona em qualquer banco de dados, mas a subconsulta é executada para cada linha, por isso é lenta numa tabela grande. Mencione-a como alternativa e use DENSE_RANK na prática.
Solução 5: o N-ésimo Maior em Cada Grupo
A pergunta seguinte habitual: "o segundo maior salário de cada departamento". Acrescente PARTITION BY à função de janela:
WITH ranked AS (
SELECT department, name, salary,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS salary_rank
FROM employee
)
SELECT department, name, salary
FROM ranked
WHERE salary_rank = 2
ORDER BY department;
Resultado: HR – Grace (4800), IT – Eve (6500), Sales – Alice (5000).
Em Sales, Bob e Carol partilham o maior salário, por isso o segundo maior são os 5000 da Alice. Um departamento com um único salário distinto ficaria fora do resultado.
Que Solução Escolher
| Solução | N | Empates | Sem resposta | Grupos |
|---|---|---|---|---|
MAX abaixo do MAX | Só 2 | Sim | NULL | Difícil |
DISTINCT ... LIMIT OFFSET | Qualquer | Com DISTINCT | Vazio; NULL numa subconsulta escalar | Não |
DENSE_RANK() | Qualquer | Sim | Vazio | PARTITION BY |
| Subconsulta correlacionada | Qualquer | Sim | Vazio | Acrescentar condição |
Numa entrevista, comece por DENSE_RANK, explique por que não ROW_NUMBER nem RANK, e diga como devolveria NULL.
Prática
- Ranking dos Salários dos Funcionários
- Funcionários Mais Bem Pagos por Departamento
- Filmes com Maior Custo de Reposição
Principais conclusões:
- O segundo maior valor é
MAX(x) WHERE x < (SELECT MAX(x) ...); trata empates e devolveNULLquando não existe. - Com
LIMIT 1 OFFSET N-1, use sempreDISTINCT, ou os empates desviam a resposta. - Uma subconsulta escalar transforma "nenhuma linha" em
NULL. - O
DENSE_RANK()resolve qualquer N, devolve os funcionários e, comPARTITION BY, funciona por grupo. ROW_NUMBEReRANKdão respostas erradas quando os valores se repetem.
Perguntas Frequentes
Como encontrar o segundo maior salário em SQL?
A consulta portável mais curta é SELECT MAX(salary) FROM employee WHERE salary < (SELECT MAX(salary) FROM employee);. Devolve o maior salário abaixo do máximo, ignora empates e devolve NULL quando todos ganham o mesmo.
Como encontrar o N-ésimo maior salário em SQL?
Use DENSE_RANK() OVER (ORDER BY salary DESC) numa CTE e filtre pela posição N, ou SELECT DISTINCT salary ... ORDER BY salary DESC LIMIT 1 OFFSET N-1. Ambas tratam corretamente os salários repetidos.
Por que não usar ROW_NUMBER ou RANK?
O ROW_NUMBER numera de forma diferente salários iguais, por isso um empate no topo coloca o mesmo valor em segundo lugar. O RANK salta números depois dos empates: com dois salários máximos, não existe posição 2. O DENSE_RANK numera os valores distintos de forma contínua.
Como devolver NULL se não houver N-ésimo maior salário?
Coloque a consulta dentro de uma subconsulta escalar: SELECT (SELECT DISTINCT salary FROM employee ORDER BY salary DESC LIMIT 1 OFFSET 9) AS result;. Quando a consulta interna não encontra nada, a externa devolve na mesma uma linha com NULL.