🙏 Obrigado pelo apoio. No mês passado, recebemos apenas US$ 32. Isso cobre os servidores, mas não o desenvolvimento, então não haverá novas lições nem recursos este mês. Se puder, contribua para o próximo mês. Apoiar →
Código SQL copiado para a área de transferência

Tempo de leitura: ~9 minutos

Esta lição foca nas funções de classificação ROW_NUMBER, RANK, DENSE_RANK e NTILE. Você aprenderá como cada função se comporta quando há empates e quando usar cada uma na prática. Ao final da lição, você conseguirá montar classificações precisas para relatórios, listas top-N e segmentação de clientes.

Usar ROW_NUMBER, RANK, DENSE_RANK e NTILE

Na lição anterior, apresentamos funções de janela e exploramos ROW_NUMBER(). Agora vamos aprofundar a família de funções de classificação que SQL oferece: ROW_NUMBER, RANK, DENSE_RANK e NTILE. Cada uma tem um propósito distinto e entender quando usar cada uma é crucial para análise de dados eficaz.

Compreender as Diferenças

As quatro funções atribuem um valor numérico às linhas com base na ordenação, mas lidam com empates (valores idênticos) de forma diferente. Vamos explorar cada uma.

ROW_NUMBER(): Números Sequenciais Únicos

ROW_NUMBER() atribui um número sequencial único a cada linha, mesmo se os valores forem idênticos. Ela trata empates como linhas diferentes.

Sintaxe:

ROW_NUMBER() OVER (
    [PARTITION BY expressao_particao]
    ORDER BY expressao_ordenacao
)

Exemplo: Classificar Transações

SELECT
    customer_id,
    amount,
    payment_date,
    ROW_NUMBER() OVER (
        PARTITION BY customer_id 
        ORDER BY amount DESC
    ) AS payment_rank
FROM
    payment
WHERE
    customer_id IN (1, 2, 3)
ORDER BY
    customer_id,
    payment_rank;

Exemplo de Resultado:

customer_id | amount | payment_date | payment_rank
1           | 11.99  | 2005-08-01   | 1
1           | 11.99  | 2005-07-08   | 2
1           | 10.99  | 2005-06-19   | 3
2           | 11.99  | 2005-08-02   | 1
2           | 10.99  | 2005-07-09   | 2
3           | 9.99   | 2005-08-03   | 1

Ponto-chave: Embora os primeiros dois pagamentos do cliente 1 tenham montantes idênticos (11.99), eles recebem números de linha diferentes (1 e 2).

RANK(): Classificação com Lacunas

RANK() atribui o mesmo classificação de linhas com valores de ordenação idênticos, mas deixa lacunas na sequência de numeração. Se duas linhas empatarem na classificação 1, o próximo é 3 (pulando 2).

Sintaxe:

RANK() OVER (
    [PARTITION BY expressao_particao]
    ORDER BY expressao_ordenacao
)

Exemplo: Classificar Pagamentos por Valor

SELECT
    customer_id,
    amount,
    payment_date,
    RANK() OVER (
        PARTITION BY customer_id 
        ORDER BY amount DESC
    ) AS payment_rank
FROM
    payment
WHERE
    customer_id IN (1, 2, 3)
ORDER BY
    customer_id,
    payment_rank;

Exemplo de Resultado:

customer_id | amount | payment_date | payment_rank
1           | 11.99  | 2005-08-01   | 1
1           | 11.99  | 2005-07-08   | 1
1           | 10.99  | 2005-06-19   | 3
2           | 11.99  | 2005-08-02   | 1
2           | 10.99  | 2005-07-09   | 2
3           | 9.99   | 2005-08-03   | 1

Ponto-chave: Ambos os pagamentos do cliente 1 de 11.99 recebem classificação 1, e o próximo pagamento recebe classificação 3 (não 2). Isso é útil quando você quer identificar empates mas preservar a posição de classificação no conjunto de dados completo.

DENSE_RANK(): Classificação sem Lacunas

DENSE_RANK() é semelhante a RANK() mas não pula números. Se duas linhas empatarem na classificação 1, o próximo é 2 (não 3).

Sintaxe:

DENSE_RANK() OVER (
    [PARTITION BY expressao_particao]
    ORDER BY expressao_ordenacao
)

Exemplo: Classificação Densa de Montantes de Pagamento

SELECT
    customer_id,
    amount,
    payment_date,
    DENSE_RANK() OVER (
        PARTITION BY customer_id 
        ORDER BY amount DESC
    ) AS payment_rank
FROM
    payment
WHERE
    customer_id IN (1, 2, 3)
ORDER BY
    customer_id,
    payment_rank;

Exemplo de Resultado:

customer_id | amount | payment_date | payment_rank
1           | 11.99  | 2005-08-01   | 1
1           | 11.99  | 2005-07-08   | 1
1           | 10.99  | 2005-06-19   | 2
2           | 11.99  | 2005-08-02   | 1
2           | 10.99  | 2005-07-09   | 2
3           | 9.99   | 2005-08-03   | 1

Ponto-chave: Ambos os pagamentos do cliente 1 de 11.99 recebem classificação 1, e o próximo montante distinto recebe classificação 2. Sem lacunas na sequência de classificação. Isso é ideal quando você quer identificar grupos distintos sem lacunas.

NTILE(): Distribuindo Linhas em Grupos

NTILE(n) divide a partição em n grupos (baldes) e atribui a cada linha um número de balde. Isso é útil para análise de percentil e agrupamento de dados em quartis, etc.

Sintaxe:

NTILE(numero_de_baldes) OVER (
    [PARTITION BY expressao_particao]
    ORDER BY expressao_ordenacao
)

Exemplo: Análise de Quartil

SELECT
    customer_id,
    amount,
    payment_date,
    NTILE(4) OVER (
        PARTITION BY customer_id 
        ORDER BY amount DESC
    ) AS quartile
FROM
    payment
WHERE
    customer_id IN (1, 2, 3)
ORDER BY
    customer_id,
    quartile;

Exemplo de Resultado:

customer_id | amount | payment_date | quartile
1           | 11.99  | 2005-08-01   | 1
1           | 11.99  | 2005-07-08   | 2
1           | 10.99  | 2005-06-19   | 3
2           | 11.99  | 2005-08-02   | 1
2           | 10.99  | 2005-07-09   | 2
3           | 9.99   | 2005-08-03   | 1

Ponto-chave: As linhas são distribuídas em 4 quartis. Isso é extremamente útil para análise de percentil—identificando top 25% (quartil 1), próximos 25% (quartil 2), etc.

Comparação Lado a Lado

Vamos ver as quatro funções aplicadas aos mesmos dados:

SELECT
    customer_id,
    amount,
    row_number() OVER (ORDER BY amount DESC) AS row_num,
    rank() OVER (ORDER BY amount DESC) AS rnk,
    dense_rank() OVER (ORDER BY amount DESC) AS dense_rnk,
    ntile(3) OVER (ORDER BY amount DESC) AS tertile
FROM
    payment
LIMIT 10;

Exemplo de Resultado:

customer_id | amount | row_num | rnk | dense_rnk | tertile
1           | 11.99  | 1       | 1   | 1         | 1
1           | 11.99  | 2       | 1   | 1         | 1
2           | 11.99  | 3       | 1   | 1         | 1
5           | 10.99  | 4       | 4   | 2         | 1
6           | 10.99  | 5       | 4   | 2         | 1
3           | 9.99   | 6       | 6   | 3         | 2
4           | 9.99   | 7       | 6   | 3         | 2
7           | 8.99   | 8       | 8   | 4         | 3
8           | 8.99   | 9       | 8   | 4         | 3
9           | 7.99   | 10      | 10  | 5         | 3

Observações:

  • row_number: Sempre único, sem lacunas
  • rank: Agrupa empates mas cria lacunas (1, 1, 1, 4, 4, 6, 6, 8, 8, 10)
  • dense_rank: Agrupa empates sem lacunas (1, 1, 1, 2, 2, 3, 3, 4, 4, 5)
  • ntile(3): Distribui em 3 grupos com base na ordenação

Aplicações Práticas

Encontrar Melhores Performers (ROW_NUMBER)

Obter o cliente com maior pagamento por mês de aluguel:

WITH ranked_payments AS (
    SELECT
        customer_id,
        amount,
        DATE_TRUNC('month', payment_date) AS month,
        ROW_NUMBER() OVER (
            PARTITION BY DATE_TRUNC('month', payment_date)
            ORDER BY amount DESC
        ) AS rank
    FROM
        payment
)
SELECT
    customer_id,
    amount,
    month
FROM
    ranked_payments
WHERE
    rank = 1
ORDER BY
    month DESC;

Identificar Níveis de Performance (DENSE_RANK)

Categorizar filmes por frequência de aluguel:

WITH rental_counts AS (
    SELECT
        film_id,
        COUNT(*) AS rental_count,
        DENSE_RANK() OVER (
            ORDER BY COUNT(*) DESC
        ) AS popularity_tier
    FROM
        rental r
        JOIN inventory i ON r.inventory_id = i.inventory_id
    GROUP BY
        film_id
)
SELECT
    film_id,
    rental_count,
    CASE
        WHEN popularity_tier = 1 THEN 'Blockbuster'
        WHEN popularity_tier <= 3 THEN 'Popular'
        WHEN popularity_tier <= 10 THEN 'Padrão'
        ELSE 'Nicho'
    END AS popularity_category
FROM
    rental_counts
LIMIT 20;

Análise de Percentil (NTILE)

Segmentar clientes em quartis de gastos:

WITH customer_spending AS (
    SELECT
        customer_id,
        SUM(amount) AS total_spent,
        NTILE(4) OVER (ORDER BY SUM(amount)) AS spending_quartile
    FROM
        payment
    GROUP BY
        customer_id
)
SELECT
    spending_quartile,
    COUNT(*) AS customer_count,
    MIN(total_spent) AS low_amount,
    MAX(total_spent) AS high_amount
FROM
    customer_spending
GROUP BY
    spending_quartile
ORDER BY
    spending_quartile;

Quando Usar Cada Função

FunçãoCaso de UsoLida com Empates
ROW_NUMBERPrecisa de números sequenciais únicos; não se importa com empatesNão (todos únicos)
RANKPrecisa identificar posição mas levar em conta empates; lacunas são OKSim (com lacunas)
DENSE_RANKPrecisa de identificação de nível sem lacunas de posiçãoSim (sem lacunas)
NTILEPrecisa de análise de percentil/quartil/grupoDistribui em grupos

Perguntas Frequentes

Quando devo escolher RANK em vez de DENSE_RANK?

Use RANK quando lacunas forem aceitáveis e você quiser uma classificação estilo competição. Use DENSE_RANK quando precisar de níveis compactos sem lacunas.

Posso usar ROW_NUMBER sem PARTITION BY?

Sim. Nesse caso, a numeração vale para todo o conjunto de resultados como uma única partição.

Por que eu precisaria de NTILE se já tenho funções de classificação?

NTILE resolve outro problema: ele divide as linhas em um número fixo de grupos, como quartis ou decis.


Perguntas de Entrevista

Qual é a diferença entre ROW_NUMBER e RANK?

ROW_NUMBER sempre atribui um número único a cada linha, enquanto RANK dá a mesma classificação para valores iguais e pode pular números.

Como funciona NTILE(4)?

Ele ordena as linhas dentro da janela e as distribui em quatro grupos aproximadamente iguais, atribuindo a cada linha um número de quartil de 1 a 4.

Como obter as N primeiras linhas de cada grupo?

Use ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) em uma subconsulta e filtre com WHERE rn <= N.


Pontos-Chave a Lembrar

  • ROW_NUMBER() fornece a cada linha um número único, útil para obter os N melhores registros de cada grupo.
  • RANK() atribui a mesma classificação aos valores empatados, mas pula classificações (1, 1, 3), útil para rankings competitivos.
  • DENSE_RANK() atribui a mesma classificação aos valores empatados sem lacunas (1, 1, 2), útil para identificação de nível.
  • NTILE(n) divide linhas em baldes para análise de percentil e distribucional.
  • Todas as quatro funções fazem parte da família de funções de janela e usam a cláusula OVER.
  • A diferença chave é como elas lidam com valores idênticos na coluna de ordenação.
  • Escolher a função correta depende do seu objetivo analítico: posicionamento, agrupamento ou distribuição.

Na próxima lição, exploraremos conceitos avançados de funções de janela, incluindo quadros de janela, estratégias de particionamento e outras funções analíticas como LAG, LEAD, FIRST_VALUE e LAST_VALUE.