26th Oct 2023 10 minutos de leitura Como usar ROW_NUMBER OVER() no SQL para classificar dados Ignacio L. Bisso aprender sql window functions Índice O que é a função ROW_NUMBER()? Uso de ROW_NUMBER() com OVER(): Um exemplo introdutório Criação de classificações com ROW_NUMBER() e ORDER BY Uso de ORDER BY duas vezes em uma consulta Uso de ROW_NUMBER() com PARTITION BY e ORDER BY Uso de ROW_NUMBER() na cláusula WHERE A pseudocoluna ROWNUM do Oracle Pronto para praticar ROW_NUMBER() e OVER() no SQL? Às vezes, você precisa saber a posição das linhas em um conjunto de resultados. Saiba como usar ROW_NUMBER e OVER no SQL pode fazer isso acontecer! Você já precisou adicionar um número sequencial aos registros retornados por uma consulta SQL? Ou talvez você precise criar um relatório "top n" com base em uma classificação específica. Em qualquer um desses casos, você precisa calcular a posição da linha na classificação. Para fazer isso, você precisa da função ROW_NUMBER(). A função atribui um número inteiro sequencial a qualquer linha do conjunto de resultados. Neste artigo, exploraremos como usar a função ROW_NUMBER() no SQL. O que é a função ROW_NUMBER()? ROW_NUMBER é uma função de janela no SQL. Ela é usada para adicionar números sequenciais às linhas de um conjunto de resultados. Como qualquer outra função de janela, você precisa usá-la com a cláusula OVER(). Aqui está a sintaxe: SELECT ROW_NUMBER() OVER (...) as athlete_num … FROM athletes; A cláusula OVER() tem duas subcláusulas opcionais: PARTITION BY e ORDER BY. Mostraremos exemplos usando várias cláusulas OVER diferentes. Antes de começarmos, vamos falar um pouco sobre as funções de janela em geral. As funções de janela são uma parte muito poderosa do SQL, mas não são muito conhecidas pelo usuário comum de SQL. É por isso que recomendo nosso curso interativo sobre Funções de Janela (Window Functions) em SQL. Nesse curso passo a passo, você conhecerá as funções de janela por meio de mais de 200 exercícios práticos. Ao final do curso, você se sentirá à vontade para usar as funções de janela em bancos de dados SQL. Uso de ROW_NUMBER() com OVER(): Um exemplo introdutório Vamos mostrar uma consulta SQL simples usando a função de janela ROW_NUMBER. Não há nada melhor do que esportes para ilustrar classificações, portanto, vamos supor que trabalhamos para uma empresa que organiza competições esportivas em muitos países. Primeiro, queremos atribuir um número sequencial a cada atleta; esse número será usado como ID do atleta em nossa empresa. Para evitar conflitos, não queremos que haja nenhum critério para determinar a ordem da numeração sequencial. Queremos que os números sequenciais sejam atribuídos a cada atleta de forma aleatória, não em ordem alfabética por nome, país ou esporte. Temos uma tabela chamada athlete com as colunas firstname, lastname, sport e country. A consulta para gerar um relatório que inclua um número sequencial para cada atleta é: SELECT ROW_NUMBER() OVER () as athlete_id, firstname lastname, sport, country FROM athletes; A expressão ROW_NUMBER() OVER () atribui um valor inteiro sequencial, começando com 1, a cada linha do conjunto de resultados da consulta. A ordem dos números atribuídos às linhas no resultado não é determinística se você usar a cláusula simples OVER(). (Observe que não há cláusulas adicionais como a cláusula ORDER BY ou PARTITION BY em OVER()) O primeiro registro pode ser qualquer registro da tabela; para esse registro, ROW_NUMBER retornará 1. Em seguida, o mesmo para o segundo registro, que será o número 2, e assim por diante. Abaixo está um resultado parcial da consulta: athlete_idfirstnamelastnamesportcountry 1JohnDoeMarathonUSA 2PaulSmithMarathonCanada 3LeaMcCianLong JumpIreland 4AnthonySmithMarathonCanada 5MarieDareauxLong JumpFrance Antes de encerrar esta seção, gostaria de sugerir o artigo O que é a cláusula OVER no SQL, no qual você pode encontrar vários exemplos de funções de janela usando diferentes combinações da cláusula OVER. Criação de classificações com ROW_NUMBER() e ORDER BY Vamos supor que a empresa precise criar um rótulo com o número do participante para todos os atletas que participam de uma maratona. Os atletas devem ser ordenados pelo sobrenome, e a empresa deseja atribuir um número sequencial a cada atleta; os atletas usarão esses números como etiquetas em suas camisetas durante a maratona. As etiquetas devem começar em 1001. A consulta é: SELECT ROW_NUMBER() OVER (ORDER BY lastname) + 1000 as participant_label, firstname, lastname, country FROM athletes WHERE sport = 'Marathon'; Essa consulta é semelhante ao exemplo anterior. Uma diferença é a cláusula WHERE, que retorna apenas os atletas participantes da maratona. A outra diferença (que é a principal) é a cláusula OVER(ORDER BY lastname). Isso indica para ROW_NUMBER() que o número sequencial deve ser atribuído na ordem do lastname- por exemplo, 1 para o primeiro lastname, 2 para o segundo e assim por diante. participant_labelfirstnamelastnamecountry 1001JohnBarryIreland 1002JohnDoeUSA 1003PaulSmithCanada 1004AnthonySmithCanada No conjunto de resultados anterior, os participantes foram ordenados por lastname. Entretanto, se dois participantes tiverem o mesmo sobrenome (ou seja, Smith), a ordem dessas duas linhas não é determinística; as linhas podem estar em qualquer ordem. Se quisermos ordenar por lastname e firstname, devemos usar a expressão: ROW_NUMBER() OVER (ORDER BY lastname, firstname) Uso de ORDER BY duas vezes em uma consulta Na consulta acima, usamos a cláusula ORDER BY na função ROW_NUMBER(). No entanto, o resultado da consulta não segue nenhuma ordem, ou seja, as linhas são ordenadas aleatoriamente. Se quiséssemos, poderíamos adicionar uma segunda cláusula ORDER BY no final da consulta para definir a ordem em que os registros do resultado são exibidos. Vamos modificar a consulta anterior adicionando uma única alteração: Colocaremos um ORDER BY country: SELECT ROW_NUMBER() OVER (ORDER BY lastname ASC) + 1000 as participant_label, firstname, lastname, country FROM athletes WHERE sport = 'Marathon' ORDER BY country; As linhas no resultado abaixo são as mesmas linhas da consulta anterior, mas são exibidas em uma ordem diferente. Agora elas estão ordenadas com base no país do atleta. Entretanto, se dois ou mais atletas forem do mesmo país, eles serão exibidos em qualquer ordem. Podemos ver isso abaixo nos dois atletas do Canadá: participant_labelfirstnamelastnamecountry 1002PaulSmithCanada 1003AnthonySmithCanada 1001JohnBarryIreland 1001JohnDoeUSA Nessa consulta, usamos a cláusula ORDER BY duas vezes. A primeira vez foi usada na função ROW_NUMBER para atribuir o número sequencial seguindo a ordem do sobrenome. A segunda vez foi usada para definir a ordem em que as linhas de resultado são mostradas, que é baseada no nome do país. Uso de ROW_NUMBER() com PARTITION BY e ORDER BY No próximo exemplo de consulta, usaremos ROW_NUMBER() combinado com as cláusulas PARTITION BY e ORDER BY. Mostraremos uma consulta para atribuir números de quartos aos atletas. Vamos supor que a empresa queira acomodar atletas do mesmo país em quartos de hotel contíguos. A ideia é criar uma etiqueta com o país e um número sequencial para cada atleta e colocar essa etiqueta na porta de cada quarto de hotel. Por exemplo, se o país for o Canadá e tiver 3 atletas, queremos que as etiquetas dos quartos sejam "Canada_1", "Canada_2" e "Canada_3". A consulta para gerar os rótulos dos quartos com o nome do atleta atribuído a esse quarto é: SELECT country || '_' || ROW_NUMBER() OVER (PARTITION BY country ORDER BY lastname ASC) as room_label, firstname, lastname, country FROM athletes; O novo elemento introduzido na consulta é OVER(PARTITION BY country). Ele agrupa as linhas do mesmo country e gera uma série sequencial diferente de números (a partir de 1) para cada país. No resultado da consulta a seguir, você pode ver que as linhas agrupadas pela cláusula PARTITION BY têm a mesma cor. Um grupo de linhas é para o Canadá (azul claro), outro para a França (roxo) e assim por diante. Dentro de cada grupo de linhas, a cláusula ORDER BY lastname é usada para atribuir números sequenciais aos atletas por sobrenome. Para a "Irlanda", temos três linhas; a primeira é para "Barry", a segunda é para "Fox" e assim por diante. room_labelfirst_namelast_namecountry Canada_1AnthonySmithCanada Canada_2PaulSmithCanada France_1MarieDareauxFrance Ireland_1JohnBarryIreland Ireland_2SeanFoxIreland Ireland_3LeaMcCianIreland USA_1JohnDoeUSA Recomendo o artigo Como usar SQL PARTITION BY com OVER, onde você pode encontrar mais exemplos das cláusulas OVER e PARTITION BY. Outras classificações Funções de Janela (Window Functions) em SQL: RANK e DENSE_RANK Além de ROW_NUMBER, o SQL fornece duas outras funções de janela para calcular classificações: RANK e DENSE_RANK. A função RANK funciona de forma diferente de ROW_NUMBER quando há empates entre as linhas. Quando há um empate, RANK atribui o mesmo valor a ambas as linhas e ignora a próxima classificação (por exemplo, 1, 2, 2, 2, 5 - as classificações 3 e 4 são omitidas). A função DENSE_RANK não pula a(s) próxima(s) classificação(ões). Vamos dar uma olhada em um exemplo simples para ver as diferenças entre essas três funções: SELECT lastname AS athlete_name, time, ROW_NUMBER() OVER (ORDER BY time) AS position_using_row_number, RANK OVER() (ORDER BY time) AS position_using_rank, DENSE_RANK() OVER (ORDER BY time) AS position_using_dense_rank FROM competition_results WHERE sport = ‘Marathon men’; Os resultados são: athlete_nametimeposition_using_row_numberposition_using_rankposition_using_dense_rank Paul Smith1h 58m 02.56s111 John Doe1h 59m 23.55s222 Anthony Smith1h 59m 23.55s322 Carlos Perez2h 1m 11.22s443 Se você estiver interessado nas funções de janela RANK e DENSE_RANK, sugiro estes artigos para obter mais detalhes e exemplos: Visão geral das funções de classificação no SQL Como usar RANK Funções O que é a função RANK no SQL e como usá-la Uso de ROW_NUMBER() na cláusula WHERE No SQL, não é possível usar funções de janela na cláusula WHERE. Entretanto, em alguns cenários, talvez seja necessário. Em um relatório Top 10, por exemplo, seria muito útil poder usar uma condição como WHERE ROW_NUMBER OVER() <= 10. Embora não seja possível usar o ROW_NUMBER() diretamente no WHERE, você pode fazê-lo indiretamente por meio de uma expressão de tabela comum, ou CTE. Por exemplo, suponha que desejemos obter as três primeiras posições na maratona e na corrida de 100 metros. Primeiro, escrevemos o CTE, que começa com WITH: -- CTE starts WITH positions AS ( SELECT lastname AS athlete_name, sport, country, time, ROW_NUMBER OVER (PARTITION BY sport ORDER BY time) AS position FROM competition_results WHERE sport IN (‘Marathon men’, ‘Marathon women’) ) --CTE ends --main query starts SELECT sport, athlete_name, time, country, position FROM positions WHERE position <= 3 ORDER BY sport, position; Na consulta anterior, criamos um CTE chamado positions. Ele tem uma coluna chamada position que é preenchida com o resultado da função ROW_NUMBER(). Na consulta principal (ou seja, a segunda instrução SELECT ), podemos usar a coluna position na cláusula WHERE para filtrar os atletas que terminam a competição nas três primeiras posições. Observação: se houver empates entre dois competidores, a função RANK() pode ser mais apropriada para uso do que a função ROW_NUMBER() neste relatório. Os resultados da consulta são mostrados abaixo: sportathlete_nametimecountryposition Marathon menPaul Smith1h 58m 02.56sCanada1 Marathon menJohn Doe1h 59m 23.55sUSA2 Marathon menAnthony Smith1h 59m 23.55sCanada3 Marathon womenMarie Dareaux2h 14m 11.22sFrance1 Marathon womenZui Ru2h 16m 36.63sKenia2 Marathon womenLea Vier2h 17m 55.87sPeru3 Se quiser praticar as funções de janela do SQL, recomendo nosso conjunto de práticas interativas Funções de Janela (Window Functions) em SQL. Ele oferece 100 exercícios práticos sobre funções de janela, incluindo a criação de classificações usando diferentes funções de janela de classificação. A pseudocoluna ROWNUM do Oracle O Oracle SQL nos permite colocar uma pseudocoluna chamada ROWNUM em qualquer consulta. Uma pseudocoluna se comporta como uma coluna de tabela, mas não é de fato armazenada na tabela. Você pode selecionar a partir de uma pseudocoluna como se fosse uma coluna da tabela. A pseudocoluna ROWNUM retorna a posição da linha no conjunto de resultados. Ela começa com 1 para a primeira linha e cada um dos registros seguintes é incrementado em 1. Entretanto, o Oracle ROWNUM não tem o poder da função de janela ROW_NUMBER. Por exemplo, você não pode usar a subcláusula PARTITION BY para criar várias sequências diferentes, como fizemos na consulta sobre quartos de hotel. Outra limitação é que você não pode usar a cláusula ORDER BY para especificar uma ordem diferente da ordem do conjunto de resultados para a sequência. A razão para essas limitações é simples: ROWNUM não é uma função de janela; é apenas uma pseudocoluna simples. Pronto para praticar ROW_NUMBER() e OVER() no SQL? Abordamos várias maneiras de adicionar uma sequência numérica ao resultado de uma consulta usando a função ROW_NUMBER. E mostramos diferentes maneiras de usar a cláusula OVER(). Também apresentamos mais duas funções de classificação do SQL: RANK e DENSE_RANK. As funções do Windows são um recurso poderoso no SQL. Se quiser se aprofundar mais, sugiro que faça nosso curso on-line interativo Funções de Janela (Window Functions) em SQL curso interativo on-line. Trata-se de um tutorial passo a passo que o conduz pelas funções de janela do SQL usando exemplos e exercícios. Também recomendo nossa Folha de consulta gratuita sobre funções SQL Windows, que é a minha folha de consulta preferida. Eu a tenho presa na parede do meu escritório para usá-la como ajuda rápida para a sintaxe da função de janela. Tags: aprender sql window functions