MongoDB – Buscar termo em todos os campos

Ao desenvolver uma API, certamente você se deparou com a missão de ter que fazer uma busca genérica em todos os campos do seu banco.

A primeira vista, essa missão pode assustar, pois, você deve pensar, como eu vou fazer isso? E se eu tiver vários campos, vou ter que especificar tudo na query?

Nesse artigo eu te mostro uma forma bem tranquila de fazer isso e ainda te digo os prós e contras.

O problema

Você está desenvolvendo uma API para o seu aplicativo e quer fazer uma busca “simples”, onde o usuário tem apenas um campo “buscar por…” e ao digitar um termo qualquer, a sua API tem que “se virar” para encontrar os resultados, seja o nome do usuário, idade, sexo, etc. não importa o que o usuário tenha digitado.

Recebendo o termo de busca

Normalmente, em uma API REST você vai receber esse termo pela querystring.

Seria alguma coisa como isso:

GET https://api.seuapp.com.br/v1/endpoint?search=termo

Se você estiver usando express do Node.js para escrever sua API, você vaii obter esse valor da seguinte forma:

app.get('endpoint', (request, response) => {
   const searchTerm = request.query.search;
});

Bacana, agora você já sabe obter o termo para a pesquisa, mas antes de nós fazermos a pesquisa no banco, vou te mostrar o pulo do gato para que isso tudo funcione de uma forma simples.

Criando o índice Text Search

Para resolver esse problema, o MongoDB nos proporciona a funcionalidade Text Search.

O Text Search nada mais é do que um índice múltiplo que quando a gente fizer a busca por ele. Veremos isso em breve.

Para criar esse índice, devemos rodar o seguinte comando no terminal do MongoDB.

db.suacolecao.createIndex( { campo1: "text", campo2: "text" } )

Para acessar o terminal do MongoDB (Mongo Client) você precisa estar com o daemon do banco de dados iniciado e digitar “mongo” no terminal do seu computador.

Pronto! Dessa forma você configurou que quando você fizer sua busca genérica, você vai pesquisar o termo nos campos: “campo1” e “campo2”.

Fique atento!

Digamos que sua coleção tenha 4 campos e você configurou no índice apenas 2 deles, fique atento, pois o termo será procurado apenas nos 2 campos configurados.

Como fazer a query?

Com a confiugração do índice, você “disse” para o MongoDB onde ele deve pesquisar o termo quando for procurado no índice text e para fazer essa busca, você precisa usar esse código.

mongoClient.find({ $text: { $search: searchTerm }});

Bônus: Como melhorar a sua experiência com a busca em índices

É possível que você quera excluir algum termo da busca ou até mesmo pesquisar por um termo exato.

Você pode excluir termos da sua busca, apenas colocando um sinal de menos na frente do termo.

db.suaColecao.find( { $text: { $search: "gol fusca -brasilia" } } )

Essa busca retorna tudo que contenha gol, fusca e que não contenha brasilia.

Você também pode procurar por um termo específico.

db.stores.find( { $text: { $search: "\"brasilia amarela\"" } } )

Esse código vai trazer apenas os resultados que contenham “brasilia amarela”.

Você também pode ordenar os resultados conforme o score da sua busca. O score é uma espécie de pontuação na qual identifica o quanto o termo encontrado é parecido ou igual ao termo pesquisado.

db.suaColecao.find(
   { $text: { $search: "gol fusca puma" } },
   { score: { $meta: "textScore" } }
).sort( { score: { $meta: "textScore" } } )

Outra forma

Existe ainda um outra forma de fazer algo parecido com isso, talvez seja a forma mais comum, entretanto, crio que não é a forma mais performática.

Essa forma não cria um índice específico pra isso, apenas adiciona os valores que devem ser pesquisados em um único campo, exemplo:

db.latasVelhas.insert(
   [
     { _id: 1, name: "Fusca", color: "azul porrada", tags: "Fusca azul porrada" },
     { _id: 2, name: "Bianco", color: "vermelho montana", tags: "Bianco vermelho montana" },
     { _id: 3, name: "Puma", color: "verde", tags: "Puma verde" },
     { _id: 4, name: "Brasilia", color: "amarela", tags: "Brasilia amarela" },
     { _id: 5, name: "Rural", color: "azul e branca", tags: "Rural azul e branca" }
   ]
)

E a busca seria algo como:

db.latasVelhas.find({ tags: /Puma/ });

O que muda nessa coleção é que não temos um índice configurado para fazer o trabalho pra gente, nesse caso a gente faz o trabalho do índice manualmente colocando todos os termos relevantes em um campo dentro da coleção.

Apesar de eu achar que é uma “gambiarra”, funciona! Mas veremos a seguir os prós e contras de ambas as formas.

Prós Vs. Contras

Tudo na vida tem prós e contras e com essa funcionalidade não é diferente.

Se você optar por criar o índice Text Search, a cada vez que houver a necessidade de adicionar um novo campo à busca, você terá que recriar esse índice e isso pode ser bem custoso, entretanto, seu código fica mais limpo, você não precisa escrever nada a mais ao inserir ou editar um dado, isto é, a manutenção é bem menos custosa, fora a velocidade que um índice traz para a sua aplicação.

Se você optar por ter um campo de tags com os termos relevantes, você vai aumentar o tamanho da sua base de forma desnecessária, fora toda a manutenção que esse campo vai exigir quando você inserir, mas principalmente quando precisar editar esses dados. Também sabemos que os índices foram criados para melhorar a performance e nesse caso você não os tem.

Mas nem pense em criar o campo de tags e ainda colocar um índice nele. Essa seria sua pior opção, tendo em vista que a cada modificação você precisara recriar os índices.

Exemplos

Considere a coleção abaixo:

db.latasVelhas.insert(
   [
     { _id: 1, name: "Fusca", color: "azul porrada" },
     { _id: 2, name: "Bianco", color: "vermelho montana" },
     { _id: 3, name: "Puma", color: "verde" },
     { _id: 4, name: "Brasilia", color: "amarela" },
     { _id: 5, name: "Rural", color: "azul e branca" }
   ]
)

Configure o índice Text Search:

db.latasVelhas.createIndex({ name: "text", color: "text" });

Busque por algum termo

db.latasVelhas.find( { $text: { $search: "fusca amarela" } } )

Busque por um termo específico

db.latasVelhas.find( { $text: { $search: "\"Bianco\"" } } )

Referência

Esse artigo foi escrito baseado na documentação do MongoDB que pode ser consultada no link abaixo (em inglês).

https://docs.mongodb.com/manual/text-search/