Adicionar navegação facetada a um aplicativo de pesquisa

A navegação facetada é usada para filtragem detalhada autodirigida nos resultados da consulta em um aplicativo de pesquisa quando ele oferece controles de formulário para definir o escopo da pesquisa para grupos de documentos (por exemplo, categorias ou marcas) e a IA do Azure Search fornece a estrutura de dados e os filtros para criar a experiência.

Neste artigo, conheça as etapas básicas para criar uma estrutura de navegação facetada na IA do Azure Search.

  • Definir atributos de campo no índice
  • Estruturar a solicitação e resposta
  • Adicionar controles de navegação e filtros na camada de apresentação

O código na camada de apresentação faz o trabalho pesado em uma experiência de navegação facetada. As demonstrações e exemplos listados no final deste artigo fornecem um código de trabalho que mostra como reunir tudo.

Navegação facetada em uma página de busca

As facetas são dinâmicas e retornadas em uma consulta. Uma resposta de pesquisa traz todas as categorias de faceta usadas para navegar pelos documentos no resultado. A consulta é executada primeiro e, em seguida, as facetas são extraídas dos resultados atuais e reunidas em uma estrutura de navegação facetada.

Na IA do Azure Search, as facetas são uma camada profunda e não podem ser hierárquicas. Se você não conhecer bem as estruturas e navegação facetada, o exemplo a seguir mostrará uma à esquerda. As contagens indicam o número de correspondências para cada faceta. O mesmo documento pode ser representado em várias facetas.

Screenshot of faceted search results.

As facetas podem ajudar a encontrar o que você está procurando, garantindo que você não obterá zero resultados. Como desenvolvedor, facetas permitem que você exponha os critérios de pesquisa mais úteis para navegar pelo seu índice de busca.

Habilitar facetas no índice

A definição em facetas é habilitada em uma base de campo por campo em uma definição de índice quando você define o atributo "facetable" (facetável) como “true” (verdadeiro).

Embora não seja estritamente necessário, você também deve definir o atributo "filterable" (filtrável) para poder criar os filtros necessários que retornem a experiência de navegação facetada em seu aplicativo de pesquisa.

O exemplo a seguir do índice de exemplo "Hotéis" mostra "facetable" e "filterable" em campos de cardinalidade baixa que contêm valores únicos ou frases curtas: "category" (categoria), "tags" (marcas), "rating" (classificação).

{
  "name": "hotels",  
  "fields": [
    { "name": "hotelId", "type": "Edm.String", "key": true, "searchable": false, "sortable": false, "facetable": false },
    { "name": "Description", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false },
    { "name": "HotelName", "type": "Edm.String", "facetable": false },
    { "name": "Category", "type": "Edm.String", "filterable": true, "facetable": true },
    { "name": "Tags", "type": "Collection(Edm.String)", "filterable": true, "facetable": true },
    { "name": "Rating", "type": "Edm.Int32", "filterable": true, "facetable": true },
    { "name": "Location", "type": "Edm.GeographyPoint" }
  ]
}

Escolhendo campos

As facetas podem ser calculadas por campos de valor único e por coleções. Os campos que funcionam melhor na navegação facetada têm estas características:

  • Baixa cardinalidade (um pequeno número de valores distintos que se repetem nos documentos em seu corpus de pesquisa)

  • Valores descritivos curtos (uma ou duas palavras) que serão renderizados perfeitamente em uma árvore de navegação

Os valores dentro de um campo, e não o nome do campo em si, produzem as facetas em uma estrutura de navegação facetada. Se a faceta for um campo de cadeia de caracteres chamado Cor, as facetas serão azuis, verdes e qualquer outro valor para esse campo.

Como prática recomendada, verifique os campos quanto a valores nulos, erros de ortografia ou discrepâncias de maiúsculas e minúsculas e as versões singular e plural da mesma palavra. Por padrão, filtros e facetas não passam por análise lexical ou verificação ortográfica, o que significa que todos os valores de um campo "facetable" são facetas potenciais, mesmo que as palavras sejam diferentes por um caractere. Como opção, você pode atribuir um normalizador a um campo "filterable" e "facetable" para suavizar as variações no uso de maiúsculas e caracteres.

Padrões em REST e SDKs do Azure

Se você estiver usando um dos SDKs do Azure, o código deve definir explicitamente os atributos de campo. Por outro lado, a API REST tem como padrão os atributos de campo com base no tipo de dados. Os seguintes tipos de dados são "filterable" e "facetable" por padrão:

  • Edm.String
  • Edm.DateTimeOffset
  • Edm.Boolean
  • Edm.Int32, Edm.Int64, Edm.Double
  • Coleções dos tipos acima, por exemplo, Collection(Edm.String) ou Collection(Edm.Double)

Você não pode usar os campos Edm.GeographyPoint ou Collection(Edm.GeographyPoint) na navegação facetada. As facetas funcionam melhor em campos com baixa cardinalidade. Devido à resolução de coordenadas geográficas, é raro que dois conjuntos de coordenadas sejam iguais em um determinado conjunto de dados. Dessa maneira, as facetas não têm suporte para coordenadas geográficas. Seria necessário um campo de cidade ou região para facetar por local.

Dica

Como uma prática recomendada para o desempenho e otimização de armazenamento, desative a faceta para os campos que nunca devem ser usados como uma faceta. Em particular, os campos de cadeia de caracteres para valores únicos, como um ID ou nome de produto, devem ser definidos como "facetable": false para evitar seu uso acidental (e ineficaz) na navegação facetada. Isso é especialmente verdadeiro para a API REST que habilita filtros e facetas por padrão.

Solicitação de faceta e resposta

As facetas são especificadas na consulta e a estrutura de navegação facetada é retornada na parte superior da resposta. A estrutura de uma solicitação e resposta é bem simples. Na verdade, o trabalho real por trás da navegação facetada está na camada de apresentação, abordada em uma seção posterior.

O exemplo de REST a seguir é uma consulta não qualificada ("search": "*") que tem como escopo o índice inteiro (consulte a amostra interna de hotéis). As facetas geralmente são uma lista de campos, mas essa consulta mostra apenas uma para uma resposta mais legível abaixo.

POST https://{{service_name}}.search.windows.net/indexes/hotels/docs/search?api-version={{api_version}}
{
    "search": "*",
    "queryType": "simple",
    "select": "",
    "searchFields": "",
    "filter": "",
    "facets": [ "Category"], 
    "orderby": "",
    "count": true
}

É útil inicializar uma página de pesquisa com uma consulta aberta para preencher completamente a estrutura de navegação facetada. Assim que você passar os termos de consulta na solicitação, a estrutura de navegação facetada terá como escopo apenas as correspondências nos resultados, em vez do índice inteiro.

A resposta para o exemplo acima inclui a estrutura de navegação facetada na parte superior. A estrutura consiste em valores de "categoria" e uma contagem de hotéis para cada um. Ela é seguida pelo restante dos resultados da pesquisa, cortados aqui para fins de brevidade. Este exemplo funciona bem por vários motivos. O número de facetas para esse campo está abaixo do limite (o padrão é 10) para que todos elas apareçam e todos os hotéis no índice de 50 hotéis são representados em exatamente uma dessas categorias.

{
    "@odata.context": "https://demo-search-svc.search.windows.net/indexes('hotels')/$metadata#docs(*)",
    "@odata.count": 50,
    "@search.facets": {
        "Category": [
            {
                "count": 13,
                "value": "Budget"
            },
            {
                "count": 12,
                "value": "Resort and Spa"
            },
            {
                "count": 9,
                "value": "Luxury"
            },
            {
                "count": 7,
                "value": "Boutique"
            },
            {
                "count": 5,
                "value": "Suite"
            },
            {
                "count": 4,
                "value": "Extended-Stay"
            }
        ]
    },
    "value": [
        {
            "@search.score": 1.0,
            "HotelId": "1",
            "HotelName": "Secret Point Motel",
            "Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
            "Category": "Boutique",
            "Tags": [
                "pool",
                "air conditioning",
                "concierge"
            ],
            "ParkingIncluded": false,
        }
    ]
}

Sintaxe de facetas

Um parâmetro de consulta de faceta é definido como uma lista delimitada por vírgulas de campos de "facetable" e, dependendo do tipo de dados, pode ser mais parametrizado para definir contagens, ordens de classificação e intervalos: count:<integer>, sort:<>, interval:<integer> e values:<list>. Para obter mais detalhes sobre parâmetros de faceta, consulte "parâmetros de consulta" na API REST.

POST https://{{service_name}}.search.windows.net/indexes/hotels/docs/search?api-version={{api_version}}
{
    "search": "*",
    "facets": [ "Category", "Tags,count:5", "Rating,values:1|2|3|4|5"],
    "count": true
}

Para cada campo facetado na árvore de navegação, há um limite padrão das dez principais facetas. Esse padrão faz sentido para estruturas de navegação porque mantém a lista de valores de um tamanho gerenciável. Você pode substituir o padrão, atribuindo um valor para “count” (contagem). Por exemplo, "Tags,count:5" reduz o número de marcas na seção marcas para as cinco principais.

Somente para valores numéricos e datetime, você pode definir valores explicitamente no campo de faceta (por exemplo, facet=Rating,values:1|2|3|4|5) para separar os resultados em intervalos contíguos (intervalos com base em valores numéricos ou períodos de tempo). Como alternativa, você pode adicionar "intervalo”, como em facet=Rating,interval:1.

Cada intervalo é criado usando 0 como ponto de partida, um valor da lista como um ponto de extremidade e então recortado do intervalo anterior para criar intervalos discretos.

Discrepâncias em contagens de faceta

Em determinadas circunstâncias, você pode constatar que as contagens de facetas não são totalmente precisas, devido à arquitetura de fragmentação. Cada índice de pesquisa é distribuído em vários fragmentos, e cada fragmento relata as N principais facetas por contagem de documentos, que são combinadas em um único resultado. Como são apenas as N principais facetas para cada fragmento, é possível perder ou contar menos documentos correspondentes na resposta de facetas.

Para garantir a precisão, você pode ampliar artificialmente a contagem: <número> para um número maior, para forçar relatórios completos em cada fragmento. Você pode especificar "count": "0" para facetas ilimitadas. Ou você pode definir a "contagem" como um valor maior ou igual ao número de valores exclusivos do campo facetado. Por exemplo, se você estiver facetando em um campo "tamanho" com cinco valores exclusivos, pode definir "count:5" para garantir que todas as correspondências sejam representadas na resposta de facetas.

A desvantagem dessa solução alternativa é aumentar a latência da consulta, portanto, use-a somente quando necessário.

Camada de apresentação

No código do aplicativo, o padrão é usar parâmetros de consulta de faceta para retornar a estrutura de navegação facetada juntamente com os resultados da faceta, mais de uma expressão $filter. A expressão de filtro manipula o evento de clique e reduz ainda mais o resultado da pesquisa com base na seleção da faceta.

Combinação de faceta e de filtro

O trecho de código a seguir do arquivo JobsSearch.cs na demonstração NYCJobs adicionará o título comercial selecionado ao filtro se você selecionar um valor da faceta título comercial.

if (businessTitleFacet != "")
  filter = "business_title eq '" + businessTitleFacet + "'";

Este é outro exemplo da amostra de hotéis. O snippet de código a seguir adicionará categoyrFacet ao filtro, se um usuário selecionar um valor da faceta da categoria.

if (!String.IsNullOrEmpty(categoryFacet))
    filter = $"category eq '{categoryFacet}'";

HTML para navegação facetada

O exemplo a seguir, extraído do arquivo index.cshtml do aplicativo de exemplo NYCJobs mostra a estrutura de HTML estático para exibição da navegação facetada na página de resultados da pesquisa. A lista de facetas é criada ou recriada dinamicamente quando você envia um termo de pesquisa ou marca ou desmarca uma faceta.

<div class="widget sidebar-widget jobs-filter-widget">
  <h5 class="widget-title">Filter Results</h5>
    <p id="filterReset"></p>
    <div class="widget-content">

      <h6 id="businessTitleFacetTitle">Business Title</h6>
      <ul class="filter-list" id="business_title_facets">
      </ul>

      <h6>Location</h6>
      <ul class="filter-list" id="posting_type_facets">
      </ul>

      <h6>Posting Type</h6>
      <ul class="filter-list" id="posting_type_facets"></ul>

      <h6>Minimum Salary</h6>
      <ul class="filter-list" id="salary_range_facets">
      </ul>

  </div>
</div>

Criar HTML de forma dinâmica

O snippet de código a seguir de index.cshtml (também da demonstração NYCJobs) cria o HTML de forma dinâmica para exibir a primeira faceta: título comercial. Funções semelhantes criam dinamicamente o HTML para as outras facetas. Cada faceta tem um rótulo e uma contagem, que exibe o número de itens encontrados para o resultado dessa faceta.

function UpdateBusinessTitleFacets(data) {
  var facetResultsHTML = '';
  for (var i = 0; i < data.length; i++) {
    facetResultsHTML += '<li><a href="javascript:void(0)" onclick="ChooseBusinessTitleFacet(\'' + data[i].Value + '\');">' + data[i].Value + ' (' + data[i].Count + ')</span></a></li>';
  }

  $("#business_title_facets").html(facetResultsHTML);
}

Dicas para trabalhar com facetas

Esta seção é uma coleção de dicas e soluções alternativas que podem ser úteis.

Preservar uma estrutura de navegação por faceta de maneira assíncrona dos resultados filtrados

Um dos desafios da navegação facetada na IA do Azure Search é que as facetas existem somente para os resultados atuais. Na prática, é comum manter um conjunto estático de facetas para que o usuário possa navegar na ordem inversa, recolhendo etapas para explorar os caminhos alternativos por meio do conteúdo da pesquisa.

Embora esse seja um caso de uso comum, não é algo que a estrutura de navegação por faceta atualmente forneça por padrão. Os desenvolvedores que desejam facetas estáticas normalmente contornam a limitação emitindo duas consultas filtradas: uma com escopo para os resultados e a outra usada para criar uma lista estática de facetas para fins de navegação.

Limpar facetas

Ao criar a página de resultados da pesquisa, lembre-se de adicionar um mecanismo para limpar facetas. Se você adicionar caixas de seleção, poderá ver com facilidade como limpar os filtros. Para outros layouts, talvez seja necessário um padrão de navegação estrutural ou outra abordagem criativa. No exemplo de C# de hotéis, você pode enviar uma pesquisa vazia para redefinir a página. Por outro lado, o aplicativo de exemplo NYCJobs fornece um [X] clicável após uma faceta selecionada para limpar a faceta, que é uma fila visual mais forte para o usuário.

Recortar resultados da faceta com mais filtros

Resultados da faceta são documentos encontrados nos resultados da pesquisa que correspondem a um termo usado como faceta. No exemplo a seguir, nos resultados da pesquisa para computação em nuvem, 254 itens também têm especificação interna como um tipo de conteúdo. Os itens não são necessariamente mutuamente exclusivos. Se um item atende aos critérios de ambos os filtros, ele será contado em cada um deles. Essa duplicação é possível ao realizar a facetagem em campos Collection(Edm.String), que geralmente são usados para implementar a marcação do documento.

Search term: "cloud computing"
Content type
   Internal specification (254)
   Video (10)

Em geral, se você descobrir que os resultados da faceta costumam ser muito grandes, é recomendável adicionar mais filtros para fornecer aos usuários mais opções para limitar a pesquisa.

Uma experiência de pesquisa somente em faceta

Se o seu aplicativo utiliza apenas a navegação facetada (ou seja, sem nenhuma caixa de pesquisa), você pode marcar o campo como searchable=false, filterable=true, facetable=true para produzir um índice mais compacto. Seu índice não incluirá índices invertidos e não haverá análise de texto ou geração de tokens. Os filtros são feitos em combinações exatas no nível do caractere.

Validar entradas em tempo de consulta

Se você criar a lista de facetas dinamicamente com base na entrada do usuário não confiável, valide se os nomes dos campos facetados são válidos. Ou escape os nomes ao criar URLs usando Uri.EscapeDataString() no .NET ou o equivalente em sua plataforma de preferência.

Demonstrações e amostras

Vários exemplos incluem navegação facetada. Esta seção tem links para os exemplos e também observa qual biblioteca de clientes e idioma é usado para cada um.

Adicionar a pesquisa aos aplicativos Web (React)

Tutoriais e exemplos em C#,Pythone JavaScript incluem navegação facetada, bem como filtros, sugestões e preenchimento automático. Esses exemplos usam React para a camada de apresentação.

Código de exemplo e demonstração de NYCJobs (Ajax)

O exemplo de NYCJobs é um aplicativo ASP.NET MVC que usa o Ajax na camada de apresentação. Ele está disponível como um aplicativo de demonstração ao vivo e como código-fonte no repositório de exemplos do Azure no GitHub.