Muitas aplicações precisam dar aos usuários a capacidade de personalizar as consultas de maneiras que complementem o que as consultas de pesquisa por si só podem fazer. Neste capítulo, você aprenderá sobre filtragem, uma técnica que permite especificar que uma consulta de pesquisa seja executada apenas no subconjunto de documentos contidos em um índice que satisfaçam uma determinada condição.

Introdução às consultas booleanas

Antes de implementar filtros, você precisa entender como as consultas compostas são implementadas no Elasticsearch.

Uma consulta composta permite que um aplicativo combine duas ou mais consultas individuais, de modo que elas sejam executadas em conjunto e, se apropriado, retornem um conjunto combinado de resultados. A maneira padrão de criar consultas compostas no Elasticsearch é usar uma consulta booleana.

Uma consulta booleana funciona como um invólucro para duas ou mais consultas ou cláusulas individuais. Existem quatro maneiras diferentes de combinar consultas:

  • bool.mustA cláusula deve corresponder. Se forem fornecidas várias cláusulas, todas devem corresponder (semelhante a uma operação lógica AND).
  • bool.should: quando usado sem must, pelo menos uma cláusula deve corresponder (semelhante a uma operação lógica OR). Quando combinada com must cada cláusula correspondente aumenta a pontuação de relevância do documento.
  • bool.filterSomente os documentos que correspondem à(s) cláusula(s) são considerados candidatos nos resultados da pesquisa.
  • bool.must_notSomente os documentos que não correspondem à(s) cláusula(s) são considerados candidatos nos resultados da pesquisa.

Como você provavelmente já deve ter percebido pelo que foi dito acima, as consultas booleanas envolvem um nível considerável de complexidade e podem ser usadas de diversas maneiras. Neste capítulo, você aprenderá como combinar a cláusula de pesquisa de texto completo com múltiplas correspondências, implementada nos capítulos anteriores, com um filtro que restringe os resultados a uma categoria de documentos. Lembre-se de que o conjunto de dados usado neste tutorial inclui um campo category que pode ser definido como sharepoint, teams ou github.

Adicionando um filtro a uma consulta

A consulta de correspondência múltipla atualmente implementada no aplicativo de tutorial utiliza a seguinte estrutura:

Para adicionar um filtro que restrinja esta pesquisa a uma categoria específica, a consulta deve ser expandida da seguinte forma:

Vamos analisar detalhadamente os novos componentes desta consulta.

Em primeiro lugar, a consulta multi_match foi movida para dentro de uma cláusula bool.must . A cláusula bool.must é geralmente o local onde a consulta base é definida. Observe que must aceita uma lista de consultas para pesquisar, portanto, isso permite que várias consultas de nível base sejam combinadas quando desejado.

A filtragem é implementada em uma seção bool.filter , usando um novo tipo de consulta, a consulta term . Usar uma consulta match ou multi_match para um filtro não é uma boa ideia, porque essas são consultas de pesquisa de texto completo. Para fins de filtragem, a consulta deve retornar uma resposta absoluta de verdadeiro ou falso para cada documento, e não uma pontuação de relevância como fazem as consultas de correspondência.

A consulta de termo realiza uma busca exata por um valor em um campo específico. Esse tipo de consulta é útil para buscar identificadores, rótulos, tags ou, como neste caso, categorias.

Essa consulta não funciona bem com campos que estão indexados para pesquisa de texto completo. Os campos de texto recebem um tipo padrão de texto e têm seu conteúdo analisado e separado em palavras individuais antes de serem indexados. O Elasticsearch atribui aos campos de string um tipo secundário de palavra-chave, que indexa o conteúdo do campo como um todo, tornando-os mais apropriados para filtragem com a consulta term . Ao usar um nome de campo de category.keyword na parte do filtro da consulta, a variante tipada keyword do campo é usada em vez da variante padrão text .

Especificando um filtro

Antes que a consulta filtrada possa ser implementada, é necessário adicionar uma maneira para os usuários finais inserirem o filtro desejado. A solução implementada neste tutorial procurará um padrão category:<category-name> no texto da consulta de pesquisa. Vamos adicionar uma função chamada extract_filters() ao app.py para procurar expressões de filtro:

A função aceita a consulta inserida pelo usuário e retorna uma tupla contendo os filtros encontrados na consulta e a consulta modificada após a remoção dos filtros. Para procurar o padrão do filtro, utiliza-se uma expressão regular. A função foi projetada para ser expandida com filtros adicionais.

Quando um filtro é encontrado, a lista filters é estendida com uma expressão de filtro correspondente, que neste caso é baseada na consulta term , conforme discutido acima.

Para entender melhor como essa função funciona, inicie uma sessão do Python (certifique-se de que o ambiente virtual esteja ativado primeiro) e execute o seguinte código:

A tupla retornada pela função deve ser:

Implementando a Busca Filtrada

O que resta fazer é alterar a função handle_search() para enviar uma consulta atualizada que combine a expressão de pesquisa de texto completo com um filtro, se um for fornecido pelo usuário. Abaixo está a nova versão desta função:

A consulta foi alterada para enviar uma expressão bool e a expressão de pesquisa foi movida para dentro de uma seção must abaixo dela. A função extract_filters() retorna a parte do filtro da consulta no formato que precisa ser enviado ao Elasticsearch, portanto, ela é inserida no dicionário de consultas também sob a chave de nível superior bool .

Experimente uma consulta de pesquisa como work from home category:sharepoint para ver como apenas os documentos da categoria fornecida são retornados.

Filtros de alcance

O Elasticsearch suporta uma variedade de filtros além do filtro term . Outro filtro comumente usado é o filtro range , que funciona com números e datas. Vamos adicionar um filtro year que pode ser usado para restringir os resultados com base no ano em que foram atualizados pela última vez, que é fornecido no campo updated_at .

Abaixo está uma versão atualizada da função extract_filters() que procura por category:<category> e year:<yyyy> como filtros:

Esta versão adiciona uma segunda expressão regular para encontrar year:yyyy na string de consulta. Ele cria um filtro range para o campo updated_at e define os limites inferior e superior do intervalo para o ano que é fornecido após os dois pontos, que é capturado na correspondência da expressão regular como m.group(1).

Há uma pequena complicação, porque o campo updated_at contém datas completas e, neste filtro, só é necessário analisar o ano. Felizmente, quando o filtro de intervalo é usado com um campo de data, os limites do intervalo podem ser aprimorados com cálculos de data. O sufixo ||/y que é adicionado aos parâmetros gte (limite inferior) e lte (limite superior) do intervalo indica que o valor fornecido é um ano que deve ser completado para formar uma data completa que pode ser comparada com o campo.

Com essa alteração, você pode incluir uma consulta como year:2020 work from home para ver resultados apenas do ano solicitado. A consulta também pode incluir os dois filtros, por exemplo year:2020 category:teams work from home.

A consulta de correspondência total

Antes de passar para um novo tópico, tente inserir apenas um filtro no campo de texto da consulta de pesquisa, por exemplo category:github. Infelizmente, isso não retorna nenhum resultado, mas o comportamento esperado neste caso seria receber todos os resultados que correspondem à categoria solicitada.

O que acontece é que a função extract_filters() retorna uma tupla com o(s) filtro(s) no primeiro elemento e uma string de consulta vazia no segundo elemento. A consulta multi_match recebe a string vazia e retorna uma lista vazia de resultados, porque nada corresponde a uma string vazia.

Para lidar com esse caso especial, a consulta multi_match pode ser substituída por match_all quando o texto de pesquisa estiver vazio. A versão da função handle_search() abaixo adiciona lógica para fazer isso. Atualize a função em app.py.

Com esta versão, você pode solicitar todos os documentos que correspondam a uma categoria. Observe como todos os resultados retornados apresentam a mesma pontuação de 1,0, pois não há termos de pesquisa para calcular as pontuações.

Anteriormente

Paginação

Pronto para criar buscas de última geração?

Uma pesquisa suficientemente avançada não se consegue apenas com o esforço de uma só pessoa. O Elasticsearch é impulsionado por cientistas de dados, especialistas em operações de aprendizado de máquina, engenheiros e muitos outros que são tão apaixonados por buscas quanto você. Vamos nos conectar e trabalhar juntos para construir a experiência de busca mágica que lhe trará os resultados desejados.

Experimente você mesmo(a)