Imagine que você trabalha em um e-commerce.

Alguém faz uma pergunta:

“Qual foi a última compra de cada cliente?”

A princípio, parece simples.

Temos uma tabela pedido:

pedido
----------------------
ID_PEDIDO
ID_CLIENTE
DATA_PEDIDO
VALOR_TOTAL

E precisamos encontrar a compra mais recente.

Talvez sua primeira ideia seja utilizar:

MAX(DATA_PEDIDO)

E ela faz sentido.

Mas existe um detalhe que deixa o problema mais interessante.

Não queremos apenas saber a data da última compra.

Queremos saber:

  • qual foi o pedido;
  • quando aconteceu;
  • qual foi o valor.

Agora o problema muda um pouco.

E é aqui que ROW_NUMBER() começa a fazer sentido.

Primeiro: o que MAX() consegue responder?

Se quisermos apenas a última data de compra de cada cliente, podemos fazer:

SELECT
    ID_CLIENTE,
    MAX(DATA_PEDIDO) AS DATA_ULTIMA_COMPRA
FROM pedido
GROUP BY
    ID_CLIENTE;

Teríamos algo assim:

ID_CLIENTEDATA_ULTIMA_COMPRA
12026-10-03
22026-09-28
32026-10-05

Perfeito.

Se a pergunta fosse:

Quando cada cliente comprou pela última vez?

poderíamos parar aqui.

Mas queremos outra coisa.

Queremos o pedido daquela data.

Por exemplo:

ID_CLIENTEID_PEDIDODATA_PEDIDOVALOR_TOTAL
11582026-10-03380,00
21492026-09-28720,00
31632026-10-05195,00

Não queremos apenas a maior data.

Queremos encontrar a linha que representa a última compra de cada cliente.

Pense em ordenar os pedidos de cada cliente

Imagine os pedidos do cliente 1:

ID_PEDIDODATA_PEDIDO
1012026-07-10
1242026-08-18
1422026-09-11
1582026-10-03

Se ordenarmos da compra mais recente para a mais antiga, podemos numerar:

ID_PEDIDODATA_PEDIDOPOSIÇÃO
1582026-10-031
1422026-09-112
1242026-08-183
1012026-07-104

Agora ficou fácil.

Qual é a última compra?

Aquela que recebeu o número 1.

É exatamente essa lógica que podemos construir com ROW_NUMBER().

Criando a numeração

Podemos escrever:

SELECT
    ID_PEDIDO,
    ID_CLIENTE,
    DATA_PEDIDO,
    VALOR_TOTAL,
    ROW_NUMBER() OVER (
        PARTITION BY ID_CLIENTE
        ORDER BY
            DATA_PEDIDO DESC,
            ID_PEDIDO DESC
    ) AS NUMERO_LINHA
FROM pedido;

A parte nova é:

ROW_NUMBER() OVER (
    PARTITION BY ID_CLIENTE
    ORDER BY
        DATA_PEDIDO DESC,
        ID_PEDIDO DESC
)

Vamos traduzir isso.

PARTITION BY ID_CLIENTE significa:

faça a numeração separadamente para cada cliente.

E:

ORDER BY
    DATA_PEDIDO DESC,
    ID_PEDIDO DESC

significa:

dentro de cada cliente, coloque os pedidos mais recentes primeiro.

O resultado começa a ficar assim:

ID_CLIENTEID_PEDIDODATA_PEDIDONUMERO_LINHA
11582026-10-031
11422026-09-112
11242026-08-183
21492026-09-281
21302026-08-222

Agora temos uma forma de identificar o pedido mais recente de cada cliente.

Só precisamos manter o número 1

Podemos organizar a consulta utilizando uma CTE:

WITH pedido_ordenado AS (
    SELECT
        ID_PEDIDO,
        ID_CLIENTE,
        DATA_PEDIDO,
        VALOR_TOTAL,
        ROW_NUMBER() OVER (
            PARTITION BY ID_CLIENTE
            ORDER BY
                DATA_PEDIDO DESC,
                ID_PEDIDO DESC
        ) AS NUMERO_LINHA
    FROM pedido
)
SELECT
    ID_PEDIDO,
    ID_CLIENTE,
    DATA_PEDIDO,
    VALOR_TOTAL
FROM pedido_ordenado
WHERE NUMERO_LINHA = 1;

Pronto.

Agora temos:

uma linha por cliente, representando sua última compra.

E conseguimos manter as informações daquele pedido.

Por que colocamos ID_PEDIDO na ordenação?

Observe:

ORDER BY
    DATA_PEDIDO DESC,
    ID_PEDIDO DESC

Por que não utilizamos apenas DATA_PEDIDO?

Imagine que um cliente faça dois pedidos na mesma data:

Pedido 170 → 2026-10-05
Pedido 175 → 2026-10-05

Se utilizarmos apenas a data, existe empate.

Precisamos definir qual pedido será considerado primeiro.

Por isso adicionamos:

ID_PEDIDO DESC

como critério de desempate.

⚠️ Isso pressupõe que um ID_PEDIDO maior representa um pedido posterior quando a data é igual. Se o banco possuir DATETIME com o horário real da compra, essa informação pode ser um critério melhor.

O importante é:

se existe possibilidade de empate, precisamos pensar em como ordenar essas linhas.

MAX ou ROW_NUMBER?

Agora podemos fazer uma comparação simples.

Se você precisa apenas saber:

Qual foi a data da última compra de cada cliente?

MAX() resolve muito bem.

SELECT
    ID_CLIENTE,
    MAX(DATA_PEDIDO) AS DATA_ULTIMA_COMPRA
FROM pedido
GROUP BY
    ID_CLIENTE;

Mas se a pergunta for:

Qual foi o último pedido de cada cliente e quais eram os dados desse pedido?

ROW_NUMBER() começa a ficar muito interessante.

Não porque seja “mais avançado”.

Mas porque representa melhor o problema.

O mesmo raciocínio resolve outros problemas

Agora que entendemos a ideia, podemos trocar a pergunta.

Em vez de:

última compra de cada cliente;

poderíamos procurar:

venda mais recente de cada vendedor;

maior pedido de cada cliente;

produto mais caro de cada categoria;

última movimentação de cada conta.

A estrutura mental continua parecida:

separar em grupos
        ↓
ordenar dentro de cada grupo
        ↓
numerar
        ↓
escolher a posição desejada

É isso que vale aprender.

Não apenas a sintaxe.

Um desafio para você

Imagine agora que a empresa quer:

As três compras mais recentes de cada cliente.

Você não precisa mudar toda a lógica.

Já temos os pedidos numerados:

1
2
3
4
5
...

Então pense:

Se NUMERO_LINHA = 1 retorna apenas a última compra, qual condição poderia retornar as três últimas?

Tente resolver antes de procurar a resposta.

Esse tipo de exercício ajuda muito mais do que simplesmente copiar outra consulta.

Conclusão

ROW_NUMBER() pode parecer estranho quando aparece pela primeira vez.

Mas o raciocínio por trás dele é bastante simples.

Precisávamos descobrir:

qual é a última compra de cada cliente?

Então pensamos:

  1. separar os pedidos por cliente;
  2. ordenar do mais recente para o mais antigo;
  3. numerar os pedidos;
  4. escolher o número 1.

Só depois transformamos esse raciocínio em:

ROW_NUMBER() OVER (
    PARTITION BY ID_CLIENTE
    ORDER BY DATA_PEDIDO DESC
)

Esse é o ponto que vale guardar.

Não comece tentando decorar ROW_NUMBER().

Entenda primeiro o problema que ele ajuda a resolver.

Quando a necessidade aparece, a sintaxe começa a fazer sentido.

Próximo passo

Na Coleção A Arte da Query, a prática segue exatamente essa lógica: você encontra cenários com contexto e precisa decidir como transformar a pergunta em uma consulta.

Porque no trabalho real dificilmente alguém vai pedir:

“Faça um ROW_NUMBER().”

A pergunta será muito mais parecida com:

“Qual foi a última compra de cada cliente?”

E o mais importante é conseguir olhar para essa pergunta e construir o caminho até a resposta.

Para este mini-case, mantive uma única função nova (ROW_NUMBER), duas etapas de consulta e um único desdobramento como desafio. Assim preservamos a profundidade técnica sem voltar ao tamanho dos artigos anteriores.

Link: https://artedaquery.com.br/

Tags: |

0 Comentários

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Solicitar exportação de dados

Use este formulário para solicitar uma cópia de seus dados neste site.

Solicitar a remoção de dados

Use este formulário para solicitar a remoção de seus dados neste site.

Solicitar retificação de dados

Use este formulário para solicitar a retificação de seus dados neste site. Aqui você pode corrigir ou atualizar seus dados, por exemplo.

Solicitar cancelamento de inscrição

Use este formulário para solicitar a cancelamento da inscrição do seu e-mail em nossas listas de e-mail.