Filtros facetados: como cozinhar e como servir

Sobre o que é isso 



Como fazer uma pesquisa facetada em uma loja online? Como os valores são gerados em filtros de pesquisa facetados? Como a escolha de um valor em um filtro afeta os valores em filtros adjacentes? Em busca de respostas, cheguei à quinta página de resultados de pesquisa do Google. Não encontrei informações exaustivas, tive que descobrir sozinho. O artigo descreve:



  1. como a IU reage quando o usuário usa filtros;
  2. algoritmo para geração de valores de filtro; 
  3. Modelos de consulta ElasticSearch e estruturas de índice com explicações.


Não há soluções prontas aqui. Você não pode copiar e colar. Para resolver seu próprio problema, você precisa se aprofundar.







Conceitos para deixar claro 



Pesquisa de texto completo - pesquise produtos por palavra ou frase. Para o usuário, é um campo para inserção de texto com o botão "Encontrar", que está disponível em qualquer página do site.



Pesquisa facetada - pesquisa de um produto por várias características: cor, tamanho, tamanho da memória, preço, etc. Para o usuário, é um conjunto de filtros. Cada filtro está associado a apenas uma característica e vice-versa. Os valores de filtro são todos os valores possíveis para uma característica. O usuário vê os filtros na página da seção, categoria, na página com os resultados da pesquisa de texto completo. Quando o usuário seleciona um valor, o filtro é considerado ativo.



Comportamento do filtro facetado 



Resumindo, um filtro filtra produtos e filtra opções de seleção em outros filtros. 



Filtros de produtos



É fácil com isso. O usuário selecionou:



  1. um valor vê produtos que correspondem ao valor;
  2. vários valores em um filtro, vê produtos que correspondem a pelo menos um;
  3. valores em vários filtros, vê os produtos que correspondem ao valor de cada filtro.


Em termos de álgebra booleana: existe um "E" lógico entre os filtros, um "OU" lógico entre os valores do filtro . Lógica simples. 



Opções de filtros em outros filtros



"Bem ... quais opções estão lá - exibidas, quais não - ocultas" - é assim que a empresa descreve o comportamento dos filtros. Parece lógico. Na prática, funciona assim:



  1. Vá para a seção Telefones, veja os filtros por características: Marca, Diagonal, Memória. Cada filtro contém valores. 
  2. . . 1.
  3. . , . , 2. 
  4. . . , 3.
  5. «» . 3 ..


O número de valores de filtro depende do número de produtos: quanto mais produtos com valores de características diferentes, mais valores no filtro. O usuário reduziu o número de produtos na seleção para os filtros restantes ao selecionar uma marca. Isso resultou em uma atualização das listas de valores.



Isso implica em uma regra universal: os valores do filtro são recuperados de uma seleção de produtos formada pelo restante dos filtros ativos.



Cada filtro ativo tem sua própria seleção de produtos.



Se tivermos N filtros e:



  • não estão ativos, então a amostra é geral. É o mesmo para todos os filtros e corresponde aos resultados da pesquisa;
  • M é ativo e M <N, então o número de amostras é M + 1, onde 1 é a amostra na qual todos os filtros ativos são aplicados. É o mesmo para todos os filtros inativos e coincide com os resultados da pesquisa;
  • ativo M e N = M, então o número de amostras N. Cada filtro tem sua própria amostra.


Eventualmente, quando o usuário seleciona um valor de filtro de faceta, acontece o seguinte: 



  1. uma seleção de busca de bens é formada; 
  2. os valores para filtros inativos são recuperados da seleção de pesquisa;
  3. para cada filtro ativo, uma nova amostra é formada e novos valores de filtros ativos são extraídos dela.


Surge a pergunta - como implementar isso na prática?



Implementação do Elasticsearch (ES)



As características do produto não são universais, então você não encontrará uma estrutura de índice pronta aqui para armazenar produtos ou consultas prontas. Em vez disso, haverá links para documentação explicando como construir os índices e consultas "corretos" você mesmo. “Correto” - com base na minha experiência e conhecimento. 



Tipos "corretos" de caixas de texto



No ES, estamos interessados ​​em 2 tipos de dados: 



  • texto para pesquisa de texto completo. Os campos desse tipo não podem ser usados ​​para comparação, classificação, agregação exata;
  • palavra-chave para cadeias de caracteres que estão envolvidas nas operações de comparação exata, classificação, agregação.


O ES analisa os valores no campo de texto e cria um dicionário para pesquisa de texto completo. Os valores no campo de palavra - chave são indexados conforme recebidos. A agregação e classificação estão disponíveis apenas para campos com o tipo de palavra-chave.



O usuário usa características em ambos os casos: na pesquisa de texto completo e por meio de filtros. ES não permite que você atribua 2 tipos a um único campo, mas oferece outras soluções:



campos 

PUT my_index
{
  «mappings»: {
    «properties»: {
      «some_property»: { 
        «type»: «text», // 1
        «fields»: { // 2
          «raw»: { 
            «type»: «keyword»
          }
        }
      }
    }
  }
}


  1. declaramos as características do produto como um campo do tipo  texto
  2. usando o parâmetro fields, crie um campo virtual filho da palavra-chave do tipo . Virtual, pois está presente no índice e não na descrição do produto. O ES salva automaticamente os dados no campo filho conforme são recebidos.


Então, para cada característica.  



As consultas para comparação exata, classificação e operações de agregação devem usar um campo virtual filho da palavra-chave do tipo . No exemplo, é some_property.raw . Para pesquisa de texto - pai.



copy_to .

PUT my_index
{
  «mappings»: {
    «properties»: {
      «all_properties»: { // 1
        «type»: «text»
      },      «some_property_1»: {
        «type»: «keyword»,
        «copy_to»: «all_properties» // 2 
      },
      «some_property_2»: {
        «type»: «keyword»,
        «copy_to»: «all_properties»
      }
    }
  }


  1. Crie um campo virtual com o tipo de texto no índice .    
  2. Declare cada característica como uma palavrachave com o parâmetro copy_to . Especifique o campo virtual com o valor do parâmetro. ES copia o valor de todas as características para o campo virtual quando o documento é salvo. 


Para as operações de comparação exata, classificação e agregação, você precisa usar o campo de característica, para pesquisa de texto - um campo com os valores de todas as características.



Ambas as abordagens criam campos adicionais no índice que não estão presentes na estrutura do documento original. Portanto, para criar uma consulta, você precisa conhecer a estrutura do índice.



Eu prefiro a opção copy_to . Então, para construir uma consulta de pesquisa de texto completo, basta conhecer um campo com uma cópia dos valores de todas as características. 



Inquéritos 



Para procurar produtos 



Assumiremos que a estrutura do índice é a mesma da variante copy_to . Para pesquisa de texto completo no ES, a construção de correspondência é usada , para comparação com os valores dos filtros facetados - consulta de termosa consulta booleana combina construções em uma consulta. Será algo assim:



{
  «query» : { 
    «bool»: {
      «must»: {
        «match»: { 
          «virtual_field_for_fulltext_searching»: {
            «query»: «some text»
          }
        }
      },
      «filter»: { 
        «must»: [
           {«property_1»: [ «value_1_1», …, «value_1_n»]},
          … 
           {«property_n»: [ «value_n_1», …, «value_n_m»]}
        ]
      }
    }
  }
}


query.bool.must.match pesquisa de texto completo principal 

query.bool.filter filtra para refinar a consulta principal. deve dentro significa "e" lógico entre os filtros. A matriz de valores em cada filtro é um booleano ou.   



Para valores de filtro



A cláusula de agregação dos termos agrupa os produtos por valor de característica e calcula a quantidade em cada grupo. Essa operação é chamada de agregação. A dificuldade é que para cada filtro ativo, a agregação dos  termos deve ser realizada em uma seleção de bens formada por outros filtros ativos. Para filtros inativos - em uma seleção que corresponda aos resultados da pesquisa. A construção de agregação de filtro permite que você crie uma seleção separada para cada agregação e operações de "pacote" em uma consulta.



A estrutura da solicitação será assim: 

{
  «size»: 0,
  «query» : { 
    «bool»: {
      «must»: {
        «match»: {
          «field_for_fulltext_searching»: {
            «fuzziness»: 2,
            «query»: «some text»
          }
        }
      },
      «filter»: {
      
      }
    }
  },
  «aggs» : {
    «inavtive_filter_agg» : {
      «filter» : {        … 
      },
      «aggs»: {
        «some_inavtive_filter_subagg»: { 
          «terms» : {
            «field» : «some_property»
          }
        },
        ... 
        «some_other_inavtive_filter_subagg»: {
          «terms» : {
            «field» : «some_other_property»
          }
        }
      }
      
    }, 
    «active_filter_1_agg» : {
       «filter»: {
         …        },
       «aggs»: {
          «active_filter_1_subagg»: {
             «terms» : {
                «field»: «property_1»
             }
          }
       }
    },
    …,  
    «active_filter_N_agg» : {
       «filter»: {
         …        
      },
       «aggs»: {
          «active_filter_N_subagg»: {
             «terms» : {
                «field»: «property_N»
             }
          }
       }
    }
  }
}


query.bool - consulta principal, as operações de filtragem são realizadas em seu contexto. Isso consiste de: 

  • correspondência - solicitação de pesquisa de texto completo;
  • filtros - filtros por características que não estão associadas a filtros de faceta e devem estar presentes em qualquer subconjunto. Pode ser um filtro por in_stock, is_visible, se você sempre quiser mostrar apenas os produtos em estoque ou apenas os visíveis.


aggs.inavtive_filter_agg - agregação para filtros de faceta inativos consiste em:

  • filtro -   condições por características que são formadas por filtros de faceta ativos. Junto com a consulta principal, uma seleção de bens é formada, na qual as agregações filho desta seção são realizadas; 
  • aggs é um objeto de agregação nomeado para cada filtro inativo


aggs.active_filter_1_agg - agregação para obter os valores do primeiro dos filtros de faceta ativos. Cada design está associado a um filtro de faceta. Consiste em: 

  • filtro - condições por características que são formadas por filtros de faceta ativos, exceto para o atual. Junto com a consulta principal, forma uma seleção de bens, sobre os quais é realizada a agregação filho desta seção;
  • aggs é um objeto de uma agregação de acordo com a característica do filtro de faceta atualmente ativo. 


É importante especificar "size": 0 , caso contrário, você obterá uma lista de produtos que correspondem à consulta principal sem agregações. 



Eventualmente 



Recebeu dois pedidos:



  1. para resultados de pesquisa, retorna produtos para exibir ao usuário;
  2. para valores de filtro, realiza agregação, retorna valores de filtro e o número de produtos com aquele valor.


Cada solicitação é independente, portanto, é melhor executá-las de forma assíncrona.   



PS: Admito que existem abordagens e ferramentas mais "corretas" para resolver o problema da pesquisa facetada. Eu ficaria muito grato por informações adicionais e exemplos nos comentários.



All Articles