Criando um ranking com SQL
Neste artigo você aprenderá como extrair qualquer posição de um ranking utilizando SQL.
O exemplo que será utilizado neste artigo é uma tabela de funcionários e seus respectivos salários. Vamos criar uma tabela para guardar as informações que vamos utilizar neste exemplo.
//SQL Server
CREATE TABLE funcionarios
(
id INT IDENTITY(1,1) PRIMARY KEY,
nome VARCHAR(150),
salario Numeric( 18,2 )
);
//MySQL
CREATE TABLE funcionarios (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(150) NOT NULL,
salario DECIMAL(18, 2)
)
Agora vamos inserir alguns registros de testes:
INSERT INTO funcionarios (nome, salario) VALUES ('João', 2000 );
INSERT INTO funcionarios (nome, salario) VALUES ('Lucas', 2500 );
INSERT INTO funcionarios (nome, salario) VALUES ('Matheus', 3000 );
INSERT INTO funcionarios (nome, salario) VALUES ('Thiago', 3500 );
INSERT INTO funcionarios (nome, salario) VALUES ('Paulo', 3500 );
INSERT INTO funcionarios (nome, salario) VALUES ('Simão', 4500 );
Se você quiser saber qual é o maior salário, pode utilizar a função MAX, e se deseja saber qual é o menor salário, pode utilizar a função MIN
SELECT MAX(salario) as maior_salario FROM funcionarios
SELECT MIN(salario) as menor_salario FROM funcionarios
O que desejamos saber não é apenas o maior e o menor valor, mas saber quem é o funcionário.
Os exemplos a seguir vão utilizar o SQL Server.
Vamos utilizar a função ROW_NUMBER() para numerar os resultados e informar que a ordenação é com base no campo salário, este campos será ordenado por ordem decrescente de salário, para trazer primeiro os maiores valores. Para saber quem ganha o maior salário, basta fazer desta forma:
SELECT id, nome, salario
FROM
(
Select id, nome, salario, ROW_NUMBER() OVER(Order by salario DESC) as ordem
from funcionarios
) DT
WHERE DT.ordem = 1 ;
Perceba que neste exemplo fizemos uma consulta para criar o campo ordem, e depois, sobre esta consulta, fizemos uma segunda consulta para poder utilizar o campo que foi criado na primeira consulta (o campo "ordem"), tomando este campo como referência, filtramos pelo registro que tem o campo "ordem" com o valor 1. Como os registros foram ordenados por ordem de salário, vamos ter como resposta o registro (ou registros) que possuem o maior salário.
Se você quiser saber quem tem o segudo maior salário, bastaria fazer o seguinte:
SELECT id, nome, salario
FROM
(
Select id, nome, salario, ROW_NUMBER() OVER(Order by salario DESC) as ordem
from funcionarios
) DT
WHERE DT.ordem = 2 ;
E se for necessário saber quem tem o menor salário? Para resolver este problema, vamos mudar o critério de ordenação utilizado para criar o campo ordem
SELECT id, nome, salario
FROM
(
Select id, nome, salario, ROW_NUMBER() OVER(Order by salario) as ordem
from funcionarios
) DT
WHERE DT.ordem = 1 ;
Outro recurso muito útil na hora de criar rankings é verificar quais os maiores valores repetidos. Perceba que em nosso exemplo, o valor 3500 é o segundo maior salário, e este valor é compartilhado por dois funcionários. Quando executamos a nossa consulta para ver quem ocupava o segundo lugar, o resultado foi apenas 1 registro. Isso porque a função ROW_NUMBER aplica um número único para cada registro.
Para resolver este novo problema, vamos substituir ROW_NUMBER e utilizar a função DENSE_RANK
SELECT id, nome, salario
FROM
(
Select id, nome, salario, DENSE_RANK() OVER(Order by salario DESC) as ordem
from funcionarios
) DT
WHERE DT.ordem = 2 ;
Desta forma, ao pesquisar pelo segundo maior salário, vamos ter como resultado dois funcionários, que é o resultado que estavamos esperando.
Como nossa tabela de testes tem apenas 6 registros, foi fácil saber qual era a posição que tinha os valores repetidos. Caso você queira saber quais as posições que tem valores repetidos, será necessário agrupar os resultados.
SELECT ordem
FROM
(
Select id, nome, salario, DENSE_RANK() OVER(Order by salario DESC) as ordem
from funcionarios
) DT
group by ordem having count(*) > 1
order by ordem
Caso você utilize o MySQL, estas funções existem nas versões superiores a 8. Se a sua versão é inferior a versão 8, pode chegar no mesmo resultado utilizando as consultas a seguir.
Para row_number():
SELECT id, nome, salario, ordem from (
Select id, nome, salario, (@row_number:=@row_number + 1) as ordem
from funcionarios, (SELECT @row_number:=0) AS t
order by salario DESC
) DT
WHERE ordem = 1
Para dense_rank():
SELECT id, nome, salario, ordem from(
SELECT id, nome, salario,
CASE
WHEN @prevRank = salario THEN @curRank
WHEN @prevRank := salario THEN @curRank := @curRank + 1
END AS ordem
FROM funcionarios F,
(SELECT @curRank :=0, @prevRank := NULL) r
ORDER BY salario DESC
) A WHERE ordem = 2