DEV Community

Cover image for Busca paginada agrupando dados no MongoDB
Yan.ts
Yan.ts

Posted on

Busca paginada agrupando dados no MongoDB

Today I Learned — 02/05/2022

Há uma semana venho utilizando o MongoDB para salvar dados vindos de um tópico no Kafka, para cada tópico é gerada uma nova coleciona no Mongo. Quando queremos resgatar as mensagens de um tópico precisamos fazer uma busca paginada no mongo, mas e se as mensagens do tópico forem atualizadas? por exemplo: um pagamento que estava aguardando ser confirmado e agora foi concluído? eu vou ter duas mensagens para a mesma transação, devo trazer as duas mensagens na busca paginada ou somente quando eu for buscar o histórico do pagamento que vou querer a mensagem anterior?

De um cenário parecido com esse surgiu a necessidade de uma busca paginada no MongoDB onde eu pudesse agrupar as mensagens pela mais recente e fazer buscas com filtragem e ordenação.

Para começar vamos precisar criar uma Aggregation que é uma operação que processa múltiplos documentos e retorna o resultado, ele pode receber uma pipeline de operações como a documentação do mongo fornece de exemplo:

Exemplo do uso de aggregation

Dentro da nossa aggregation vamos precisar de apenas um $facet na pipeline, como a documentação define um facet é uma forma de processar múltiplos processos em um único estagio da da pipeline do aggregation e também podemos definir como vai ser retornado o resultado:

Exemplo de um uso de facet

No nosso facet vamos precisar de dois output fields (podem ser mais se necessário mas para o meu caso só precisei de dois), o data, que contem os documents e o total, que vai retornar o total de documents para determinado filtro.

Data:

O data vai ser uma pipeline que primeiramente vai conter um $group que funciona como um order by em um banco relacional, nesse group vamos dizer que

{$group: {
 _id: '$key',
 data: {$last: '$$ROOT'}
}}
Enter fullscreen mode Exit fullscreen mode

o campo que ele vai usar para juntar os documents é o key, e ele vai inserir esses documents agrupados dentro do data, pegando o ultimo documento do grupo, que vai ser o mais recente

ps: A ordem das operações importa, primeiro fazemos o grupo para que ele consiga separar o ultimo como de fato o ultimo valor a entrar no mongo

em seguida fazemos o $sort:

{ $sort: { 'data.createdAt': 1 } },
Enter fullscreen mode Exit fullscreen mode

Onde pegamos o campo createdAt do data que foi criado pelo group e dizemos se queremos ele de forma ascendente ou descendente,

1 para ascendente e -1 para descendente

em seguida o $skip e o $limit

{ $skip: 0},
{ $limit: 10},
Enter fullscreen mode Exit fullscreen mode

O skip para informarmos quantos documento desejamos pular na paginação e o limit para quantos queremos trazer. Novamente a ordem é muito importante pois se fizermos o contrario em uma collection que só tenham 2 documents e colocarmos o limit antes do skip e no skip informamos que queremos pular 1 document, o que vai acontecer é, o mongo vai buscar apenas 1 document e o skip vai pular ele, retornando assim zero, enquanto o comportamento esperado é ele pular o primeiro document e trazer o segundo.

Count:

{
$group: {
_id: '$key',
},
Enter fullscreen mode Exit fullscreen mode

No count precisamos novamente formar o grupo para que a nossa contagem não venha com mais valores do que realmente vamos ter de dados e em seguida vamos passar um count que recebe como argumento o nome que queremos q ele devolva na query

{ $count: 'total' }
Enter fullscreen mode Exit fullscreen mode

nesse caso ele vai contar quantas entradas foram retornadas e devolver isso como um total.

No final o nosso código vai ficar mais ou menos assim, sendo que se quisermos fazer algum filtro podemos utilizar o operador de $match

 $facet: {
          data: [
            {
              $group: {
                _id: '$key',
                data: {
                  $last: '$$ROOT',
                },
              },
            },
            { $sort: { 'data.createdAt': 1 } },
            { $skip: 0 },
            { $limit: 10 },
          ],
          count: [
            {
              $group: {
                _id: '$key',
              },
            },
            { $count: 'total' },
          ],
        },
Enter fullscreen mode Exit fullscreen mode

E o retorno que o mongo vai fornecer é o seguinte onde data contem o document mais recente

Exemplo de resposta do Mongo

PS: limit e skip não é a melhor forma de fazer uma busca paginada esse artigo explora um pouco mais esse assunto https://scalegrid.io/blog/fast-paging-with-mongodb/

Top comments (0)