Capa do Ebook gratuito SQL para Análise de Dados no Dia a Dia: Consultas, Relatórios e Insights com Dados Reais

SQL para Análise de Dados no Dia a Dia: Consultas, Relatórios e Insights com Dados Reais

Novo curso

26 páginas

ROW_NUMBER e RANK para top N, empates e priorização de registros

Capítulo 13

Tempo estimado de leitura: 0 minutos

+ Exercício

Em análises do dia a dia, “pegar o top N” parece simples: ordenar e limitar. O problema começa quando você precisa lidar com empates, priorizar registros dentro de um grupo (por exemplo, “um cliente pode ter vários pedidos no mesmo dia”) ou escolher “o registro mais relevante” seguindo regras de negócio. É aí que ROW_NUMBER e RANK entram como ferramentas centrais para criar rankings consistentes, controlar empates e fazer deduplicação com critério.

O que são ROW_NUMBER e RANK (e por que não são a mesma coisa)

ROW_NUMBER() e RANK() são funções de janela usadas para numerar linhas dentro de uma partição (um grupo) conforme uma ordenação. A diferença principal está em como tratam empates.

ROW_NUMBER: numeração única, mesmo com empate

ROW_NUMBER() atribui um número sequencial único para cada linha dentro da partição, seguindo o ORDER BY da janela. Se duas linhas empatarem no critério de ordenação, ainda assim cada uma receberá um número diferente (por exemplo, 1 e 2). Isso é ideal quando você precisa escolher “apenas um” registro por grupo, mesmo que haja empate, e quer resolver o empate com um critério adicional.

RANK: empates compartilham posição e “pulam” números

RANK() atribui a mesma posição para linhas empatadas. Se duas linhas empatarem em 1º lugar, ambas recebem 1 e a próxima linha recebe 3 (há um “pulo”). Isso é ideal quando você quer respeitar empates e retornar todos os registros empatados no top N.

Quando usar cada um

  • Use ROW_NUMBER quando a regra pede um único registro por grupo (ex.: “pedido mais recente por cliente”, “último status por ticket”, “um endereço principal por usuário”).
  • Use RANK quando a regra pede top N com empates incluídos (ex.: “top 3 produtos por receita, incluindo empates”, “top 10 vendedores por vendas, sem desempatar”).

Estrutura mental: PARTITION BY e ORDER BY na janela

As duas funções seguem o mesmo padrão:

Continue em nosso aplicativo

Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.

ou continue lendo abaixo...
Download App

Baixar o aplicativo

ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)
RANK() OVER (PARTITION BY ... ORDER BY ...)

PARTITION BY define “o grupo” onde a contagem/regras reiniciam. ORDER BY define o critério de ranking dentro do grupo. Se você omitir PARTITION BY, o ranking é calculado no conjunto inteiro.

Exemplo base (tabelas fictícias)

Para os exemplos, imagine uma tabela de pedidos com colunas típicas:

  • orders(order_id, customer_id, order_date, total_amount, status)
  • order_items(order_id, product_id, quantity, unit_price)
  • products(product_id, product_name, category)

O foco aqui não é como montar as tabelas, e sim como aplicar ROW_NUMBER e RANK para top N, empates e priorização.

ROW_NUMBER na prática: “último pedido por cliente” (deduplicação com critério)

Um caso comum é: “quero uma linha por cliente, trazendo o pedido mais recente”. Se você tentar resolver isso apenas com MAX(order_date), você pode cair em ambiguidades quando há mais de um pedido na mesma data/hora ou quando precisa trazer colunas do pedido (valor, status, etc.).

Passo a passo

Passo 1: atribuir um número por cliente ordenando do mais recente para o mais antigo.

WITH ranked_orders AS (  SELECT    o.*,    ROW_NUMBER() OVER (      PARTITION BY o.customer_id      ORDER BY o.order_date DESC, o.order_id DESC    ) AS rn  FROM orders o) SELECT  * FROM ranked_orders WHERE rn = 1;

O que está acontecendo:

  • PARTITION BY customer_id reinicia a contagem para cada cliente.
  • ORDER BY order_date DESC coloca o pedido mais recente primeiro.
  • order_id DESC é um critério de desempate para garantir determinismo quando dois pedidos têm a mesma data. Sem um desempate, o banco pode escolher qualquer ordem entre empatados, e o “rn = 1” pode variar.

Resultado: uma linha por cliente, com o pedido “mais recente” conforme a regra definida.

Priorizar registros: “status mais relevante por ticket”

Outro cenário típico: uma entidade tem vários registros de status/etapas, e você precisa escolher o status “mais importante” para relatório. Exemplo: um pedido pode ter status cancelled, refunded, delivered, shipped, processing. Para um painel, você pode querer priorizar cancelled acima de delivered, e delivered acima de shipped, etc.

Passo a passo com ORDER BY por regra de negócio

Passo 1: criar uma ordenação por prioridade usando CASE.

WITH ranked_status AS (  SELECT    o.order_id,    o.customer_id,    o.status,    o.order_date,    ROW_NUMBER() OVER (      PARTITION BY o.order_id      ORDER BY        CASE o.status          WHEN 'cancelled' THEN 1          WHEN 'refunded' THEN 2          WHEN 'delivered' THEN 3          WHEN 'shipped' THEN 4          WHEN 'processing' THEN 5          ELSE 99        END,        o.order_date DESC    ) AS rn  FROM orders o) SELECT  order_id, customer_id, status, order_date FROM ranked_status WHERE rn = 1;

Ideia-chave: você não precisa ordenar apenas por data ou valor. Pode ordenar por uma “tabela mental” de prioridades. O ROW_NUMBER escolhe um único registro por pedido, respeitando a prioridade.

Cuidados práticos:

  • Garanta que a regra de prioridade seja completa (inclua ELSE).
  • Inclua um critério secundário (como data) para desempatar dentro da mesma prioridade.

Top N por grupo com ROW_NUMBER: “top 3 pedidos por cliente”

Às vezes você quer mais de um registro por grupo, mas ainda com controle. Exemplo: “top 3 pedidos de maior valor por cliente”.

Passo a passo

Passo 1: numerar pedidos por cliente do maior para o menor valor.

WITH ranked_orders AS (  SELECT    o.customer_id,    o.order_id,    o.total_amount,    o.order_date,    ROW_NUMBER() OVER (      PARTITION BY o.customer_id      ORDER BY o.total_amount DESC, o.order_date DESC, o.order_id DESC    ) AS rn  FROM orders o) SELECT  customer_id, order_id, total_amount, order_date FROM ranked_orders WHERE rn <= 3 ORDER BY customer_id, rn;

Observação importante: com ROW_NUMBER, mesmo que haja empate no valor, você ainda terá exatamente 3 linhas por cliente (se existirem 3 ou mais pedidos). Isso é útil quando o relatório precisa de um número fixo de linhas por grupo.

Top N com empates: usando RANK para “top 3 produtos por receita, incluindo empates”

Agora o cenário oposto: você quer top N, mas não quer “quebrar” empates. Exemplo: “top 3 produtos por receita no mês”. Se o 3º e o 4º produto empatarem, faz sentido listar ambos.

Passo a passo

Passo 1: calcular receita por produto.

WITH product_revenue AS (  SELECT    oi.product_id,    SUM(oi.quantity * oi.unit_price) AS revenue  FROM order_items oi  GROUP BY oi.product_id), ranked_products AS (  SELECT    pr.product_id,    pr.revenue,    RANK() OVER (ORDER BY pr.revenue DESC) AS rnk  FROM product_revenue pr) SELECT  product_id, revenue, rnk FROM ranked_products WHERE rnk <= 3 ORDER BY rnk, revenue DESC;

O que muda aqui:

  • Com RANK, se dois produtos empatarem em 3º, ambos terão rnk = 3 e serão incluídos.
  • O resultado pode ter mais de 3 linhas. Isso é esperado e desejado quando a regra é “top 3 posições” e não “3 itens”.

Top N por categoria com empates: RANK + PARTITION BY

Um relatório comum é “top N por categoria”. Aqui, você quer reiniciar o ranking para cada categoria.

WITH product_revenue AS (  SELECT    p.category,    oi.product_id,    SUM(oi.quantity * oi.unit_price) AS revenue  FROM order_items oi  JOIN products p ON p.product_id = oi.product_id  GROUP BY p.category, oi.product_id), ranked AS (  SELECT    category,    product_id,    revenue,    RANK() OVER (      PARTITION BY category      ORDER BY revenue DESC    ) AS rnk  FROM product_revenue) SELECT  category, product_id, revenue, rnk FROM ranked WHERE rnk <= 5 ORDER BY category, rnk, revenue DESC;

Interpretação: você obtém as 5 primeiras posições por categoria, incluindo empates. Se houver empate na 5ª posição, podem aparecer mais itens naquela categoria.

Empates e determinismo: por que seu top N pode “mudar sozinho”

Quando há empates no critério de ordenação, o banco pode retornar as linhas empatadas em qualquer ordem se você não especificar um desempate. Isso afeta diretamente ROW_NUMBER, porque ele precisa escolher uma ordem para numerar.

Regra prática

  • Se você usa ROW_NUMBER para selecionar rn = 1 (ou rn <= N), sempre inclua critérios adicionais no ORDER BY da janela para desempatar (por exemplo, um identificador único como order_id).
  • Se você usa RANK, o empate é preservado, mas ainda pode ser útil adicionar critérios secundários para ordenar a exibição final (não para o rank em si, se você quer manter o conceito de empate).

ROW_NUMBER para “último registro por chave” em tabelas de eventos (deduplicação de logs)

Em dados operacionais, é comum ter uma tabela de eventos com múltiplas linhas por entidade: atualizações de cadastro, mudanças de plano, alterações de endereço, etc. Um padrão robusto é: “pegar o registro mais recente por entidade” usando ROW_NUMBER.

Passo a passo genérico

Passo 1: definir a chave de partição (entidade) e o critério de recência.

WITH latest AS (  SELECT    e.*,    ROW_NUMBER() OVER (      PARTITION BY e.entity_id      ORDER BY e.updated_at DESC, e.event_id DESC    ) AS rn  FROM entity_events e) SELECT  * FROM latest WHERE rn = 1;

Por que isso funciona bem:

  • Evita subqueries complexas para “trazer colunas do registro do MAX(updated_at)”.
  • Permite desempate com um identificador do evento.
  • Escala para regras mais ricas (por exemplo, priorizar tipos de evento antes de olhar a data).

RANK para “faixas de posição” e relatórios com empates explícitos

Em alguns relatórios, a posição em si é uma informação (ex.: “quem está em 1º, 2º, 3º”). Com empates, você quer que a posição reflita isso. RANK é apropriado quando a semântica do relatório é “posição no ranking” e não “ordem arbitrária”.

Exemplo: ranking de vendedores por faturamento

WITH seller_revenue AS (  SELECT    o.seller_id,    SUM(o.total_amount) AS revenue  FROM orders o  GROUP BY o.seller_id), ranked AS (  SELECT    seller_id,    revenue,    RANK() OVER (ORDER BY revenue DESC) AS rnk  FROM seller_revenue) SELECT  seller_id, revenue, rnk FROM ranked ORDER BY rnk, seller_id;

Leitura do resultado: se dois vendedores empatarem em faturamento, ambos aparecem com o mesmo rnk. Isso evita “forçar” um desempate que não existe no dado.

Priorizar e depois ranquear: combinando regras de negócio com top N

Às vezes o top N não é apenas “maior valor”. Você pode precisar ranquear por uma regra composta: primeiro por prioridade (ex.: pedidos com status “delivered” contam, “cancelled” não), depois por valor, depois por data.

Passo a passo: top 5 pedidos “válidos” por cliente

Suponha que você queira listar os 5 pedidos mais relevantes por cliente, onde pedidos cancelados vão para o fim (ou são excluídos), e dentro dos válidos você ordena por valor e recência.

WITH ranked AS (  SELECT    o.customer_id,    o.order_id,    o.status,    o.total_amount,    o.order_date,    ROW_NUMBER() OVER (      PARTITION BY o.customer_id      ORDER BY        CASE WHEN o.status = 'cancelled' THEN 2 ELSE 1 END,        o.total_amount DESC,        o.order_date DESC,        o.order_id DESC    ) AS rn  FROM orders o) SELECT  customer_id, order_id, status, total_amount, order_date FROM ranked WHERE rn <= 5 ORDER BY customer_id, rn;

Variação comum: se cancelados não devem aparecer, filtre antes de ranquear (por exemplo, em uma CTE anterior) e use o ranking apenas nos “válidos”. A escolha depende do objetivo do relatório: excluir ou apenas rebaixar.

Checklist prático para escolher entre ROW_NUMBER e RANK em top N

  • Preciso de exatamente N linhas por grupo? Use ROW_NUMBER e filtre rn <= N.
  • Preciso de “N posições” e incluir empates? Use RANK e filtre rnk <= N.
  • Vou selecionar apenas 1 linha por grupo? Use ROW_NUMBER com desempate determinístico no ORDER BY.
  • O ranking é uma informação de negócio (posição) e empates devem ser visíveis? Use RANK.
  • Há chance de empate no critério principal? Para ROW_NUMBER, sempre adicione um desempate (id único, timestamp mais preciso, etc.).

Agora responda o exercício sobre o conteúdo:

Ao montar um relatório de top 3 produtos por receita, mas incluindo todos os empates na 3ª posição, qual abordagem é a mais adequada?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

RANK atribui a mesma posicao para valores empatados e pode pular numeros, permitindo filtrar por rnk <= N para retornar as N posicoes incluindo empates. Ja ROW_NUMBER sempre gera numeros unicos e limita a quantidade de linhas.

Próximo capitúlo

LAG e LEAD para variações, deltas e acompanhamento período a período

Arrow Right Icon
Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.