Pesquisa facetada

Com a pesquisa de atributos, é possível anexar informações categóricas a documentos. Um atributo é um par atributo/valor. Por exemplo, o atributo "tamanho" pode ter os valores "pequeno", "médio" e "grande".

Com o uso das pesquisas de atributo, é possível recuperar informações resumidas para ajudar você a refinar uma consulta e detalhar os resultados em uma série de etapas.

Isso é útil para aplicativos como sites de compras, em que você pretende oferecer um grupo de filtros para que os clientes restrinjam os produtos que eles queiram ver.

A distribuição dos valores de um atributo é mostrada pelos dados agregados dele. Por exemplo, o atributo "tamanho" pode aparecer em muitos dos documentos do respectivo grupo de resultados. Os dados agregados desse atributo podem mostrar que o valor "pequeno" aparece 100 vezes, "médio" 300 vezes e "grande" 250 vezes. Cada par atributo/valor representa um subgrupo de documentos no resultado da consulta. Uma chave, chamada de refinamento, está associada a cada par. Inclua refinamentos em uma consulta para recuperar documentos que correspondam à string de consulta e que tenham os valores de atributo correspondentes a um ou mais refinamentos.

Quando você executa uma pesquisa, pode escolher quais atributos coletar e mostrar com os resultados, ou habilitar a descoberta de atributos para selecionar automaticamente aqueles que aparecem com mais frequência nos documentos.

Como adicionar atributos a um documento

Adicione atributos a um documento antes de adicionar o documento a um índice. Faça isso ao mesmo tempo em que você especifica os campos do documento:

package com.google.test.facet;

import java.io.IOException;
import javax.servlet.http.*;
import com.google.appengine.api.search.*;

public class FacetsearchjavaServlet extends HttpServlet {
  public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    Document doc1 = Document.newBuilder()
      .setId("doc1")
      .addField(Field.newBuilder().setName("name").setAtom("x86"))
      .addFacet(Facet.withAtom("type", "computer"))
      .addFacet(Facet.withNumber("ram_size_gb", 8.0))
      .build();
    IndexSpec indexSpec = IndexSpec.newBuilder().setName("products").build();
      Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
    index.put(doc1);
  }
}

Um atributo é semelhante a um campo de documento: ele tem um nome e um valor.

Os nomes dos atributos seguem as mesmas regras dos campos de documentos: diferenciam maiúsculas de minúsculas e só podem conter caracteres ASCII. Eles precisam começar com uma letra e podem conter letras, números ou sublinhado. O nome não pode ter mais de 500 caracteres.

O valor de um atributo pode ser uma string atômica com no máximo 500 caracteres ou um número (um valor de ponto flutuante de dupla precisão entre -2.147.483.647 e 2.147.483.647).

Para atribuir vários valores a um atributo em um documento, adicione um atributo com o mesmo nome e tipo várias vezes, usando um valor diferente a cada vez.

Não há limite para o número de valores que um atributo pode ter. Também não há limite para o número de atributos que podem ser adicionados a um documento nem para o número de atributos de nome único em um índice.

Observe que, cada vez que você usa um atributo, ele pode assumir um valor atômico ou numérico. Um atributo com o nome "tamanho" pode ser anexado a um documento com o valor da string "pequeno" e a outro documento com o valor numérico 8. Na verdade, o mesmo atributo pode aparecer várias vezes no mesmo documento com ambos os tipos de valores. Não recomendamos usar valores atômicos e numéricos para o mesmo atributo, mesmo que seja permitido.

O atributo tem um tipo específico quando adicionado a um documento, mas os resultados da pesquisa juntam todos os valores. Por exemplo, os resultados para o atributo "tamanho" podem mostrar que há 100 instâncias do valor "pequeno", 150 instâncias de "médio" e 135 instâncias de valores numéricos no intervalo [4, 8). Os valores numéricos exatos e a distribuição de frequência deles não são mostrados.

Quando você recupera um documento usando uma consulta, não consegue acessar diretamente os atributos e valores dele. É preciso solicitar o retorno das informações do atributo na consulta, como explicado na próxima seção.

Como usar uma pesquisa para recuperar informações de atributos

Solicite o back-end da pesquisa para descobrir os atributos mais utilizados. Isso é chamado de descoberta automática de atributos. Também é possível recuperar informações de atributos explicitamente selecionando um atributo por nome, ou por nome e valor. Combine e misture os três tipos de recuperação de atributos em uma só consulta.

A solicitação de informações do atributo não afetará os documentos retornados pela consulta. Ela pode afetar o desempenho. Fazer uma pesquisa de atributos com a profundidade padrão de 1000 tem o mesmo efeito de configurar o limite do scorer de opções de classificação para 1000.

Descoberta automática de atributos

Com a descoberta automática, é possível procurar os atributos que aparecem com mais frequência no agregado dos documentos. Por exemplo, digamos que os documentos correspondentes à consulta incluam um atributo "cor" que aparece 5 vezes com o valor "vermelho", 5 vezes com o valor "branco" e 5 vezes com a cor "azul". Esse atributo tem uma contagem total de 15. Para fins de descoberta, ele estaria em uma classificação mais alta do que outro atributo chamado "tom", que aparece nos mesmos documentos correspondentes 6 vezes com o valor "escuro" e 7 vezes com o valor "claro".

É preciso configurar a descoberta de atributos na consulta para ativá-la:

package com.google.test.facet;

import java.io.IOException;
import javax.servlet.http.*;
import com.google.appengine.api.search.*;

public class FacetsearchjavaServlet extends HttpServlet {
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    IndexSpec indexSpec = IndexSpec.newBuilder().setName("products").build();
      Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
      Results<ScoredDocument> result = index.search(
          Query.newBuilder().setEnableFacetDiscovery(true) // enable discovery
          .build("name:x86"));
      for(FacetResult facetResult : result.getFacets()) {
        resp.getWriter().printf("Facet %s:\n", facetResult.getName());
        for (FacetResultValue facetValue : facetResult.getValues()) {
          resp.getWriter().printf("   %s: Count=%s, RefinementKey=%s\n",
              facetValue.getLabel(),
              facetValue.getCount(),
              facetValue.getRefinementToken());
        }
      }
  }
}

Quando você recupera os atributos por descoberta, apenas os dez valores mais frequentes deles são retornados por padrão. É possível aumentar esse limite até 100 usando FacetOptions.Builder.setDiscoveryValueLimit().

Observe que a descoberta automática de atributos não foi feita para retornar todos os atributos possíveis e os respectivos valores. Os atributos retornados pela descoberta podem variar a cada execução. Se você quiser um conjunto fixo de atributos, use um parâmetro return_facets na consulta.

Os valores de string são retornados individualmente. Os valores numéricos de um atributo descoberto são retornados em um único intervalo [min. máx.). É possível examinar esse intervalo e criar um menor para consulta posterior.

Como selecionar atributos pelo nome

Para recuperar informações sobre um atributo apenas por seu nome, adicione um objeto ReturnFacet à consulta, especificando o nome do atributo:

package com.google.test.facet;

import java.io.IOException;
import javax.servlet.http.*;
import com.google.appengine.api.search.*;

public class FacetsearchjavaServlet extends HttpServlet {
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    IndexSpec indexSpec = IndexSpec.newBuilder().setName("products").build();
      Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
      Results<ScoredDocument> result = index.search(Query.newBuilder()
        .addReturnFacet("type")
        .addReturnFacet("ram_size_gb")
        .build("name:x86"));
      for(FacetResult facetResult : result.getFacets()) {
        resp.getWriter().printf("Facet %s:\n", facetResult.getName());
        for (FacetResultValue facetValue : facetResult.getValues()) {
          resp.getWriter().printf("   %s: Count=%s, RefinementKey=%s\n",
              facetValue.getLabel(),
              facetValue.getCount(),
              facetValue.getRefinementToken());
        }
      }
  }
}

Quando você recuperar um atributo por nome, por padrão, somente os 10 valores mais frequentes dele serão retornados. É possível aumentar esse limite até 20 usando FacetOptions.Builder.setDiscoveryValueLimit().

Como selecionar atributos por nome e valor

Para recuperar um atributo com um valor específico, adicione um objeto ReturnFacet que inclui um FacetRequest:

package com.google.test.facet;

import java.io.IOException;
import javax.servlet.http.*;
import com.google.appengine.api.search.*;

public class FacetsearchjavaServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        IndexSpec indexSpec = IndexSpec.newBuilder().setName("products").build();
        Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
        // Fetch the "type" facet with values "computer and "printer"
        // along with the "ram_size_gb" facet with values in the ranges [0,4), [4, 8), and [8, max]
        Results<ScoredDocument> result = index.search(Query.newBuilder()
            .addReturnFacet(FacetRequest.newBuilder()
                .setName("type")
                .addValueConstraint("computer")
                .addValueConstraint("printer"))
            .addReturnFacet(FacetRequest.newBuilder()
                .setName("ram_size_gb")
                .addRange(FacetRange.withEnd(4.0))
                .addRange(FacetRange.withStartEnd(4.0, 8.0))
                .addRange(FacetRange.withStart(8.0)))
            .build("name:x86"));
        for(FacetResult facetResult : result.getFacets()) {
            resp.getWriter().printf("Facet %s:\n", facetResult.getName());
            for (FacetResultValue facetValue : facetResult.getValues()) {
                resp.getWriter().printf("   %s: Count=%s, RefinementKey=%s\n",
                        facetValue.getLabel(),
                        facetValue.getCount(),
                        facetValue.getRefinementToken());
            }
        }
    }
}

Os valores em um único FacetRequest precisam ser do mesmo tipo, ou uma lista de valores de string ou, para números, uma lista de FacetRanges, que são intervalos fechados à esquerda (início) e abertos à direita (final). Se o atributo tiver um mix de valores de string e número, adicione FacetRequests separados para cada um.

Opções

Controle a pesquisa de atributo adicionando um parâmetro FacetOptions a uma chamada de consulta. Esse parâmetro usa uma única instância de FacetOptions. Use esse parâmetro para modificar o comportamento padrão da pesquisa de atributo.

Results<ScoredDocument> results = index.search(Query.newBuilder()
  .addReturnFacet(FacetRequest.newBuilder()
    .setName("type")
    .addValueConstraint("computer")
    .addValueConstraint("printer"))
  .addReturnFacet(FacetRequest.newBuilder()
    .setName("ram_size_gb")
    .addRange(FacetRange.withEnd(4.0))
    .addRange(FacetRange.withStartEnd(4.0, 8.0))
    .addRange(FacetRange.withStart(8.0)))
  .setFacetOptions(FacetOptions.newBuilder()
    .setDiscoveryLimit(5)
    .setDiscoveryValueLimit(10)
    .setDepth(6000).build());
  .build("some_query");
Parâmetro Descrição Padrão
DiscoveryLimit Número de atributos a serem descobertos se a descoberta de atributo estiver ativada. Se 0, a descoberta de atributos será desativada. 10
DiscoveryValueLimit Número de valores a serem retornados para cada um dos atributos mais descobertos. 10
Depth O número mínimo de documentos em resultados da consulta a serem avaliados para coletar informações do atributo. 1000

A opção Depth se aplica a todos os três tipos de agregação de atributo: por nome, por nome e valor e por descoberta automática. As outras opções são apenas para descoberta automática.

Observe que a profundidade do atributo geralmente é muito maior do que o limite da consulta. Os resultados dos atributos são calculados para, no mínimo, o número da profundidade dos documentos. Se você definir o limite de pontuação das opções de classificação acima da profundidade, ele será usado no lugar.

Como recuperar resultados de atributos

Quando você usa parâmetros da pesquisa de atributos em uma consulta, a informação dos atributos agregados vem com o resultado da consulta.

Uma consulta terá uma lista de FacetResult. Haverá um resultado na lista para cada atributo exibido em um documento correspondente à consulta. Para cada resultado, você receberá:

  • O nome do atributo.
  • Uma lista dos valores mais frequentes do atributo. Para cada valor, há uma contagem de quantas vezes ele apareceu, além de uma chave de refinamento que pode ser usada para recuperar os documentos correspondentes a essa consulta e ao valor do atributo.

Observe que a lista de valores incluirá a string e os valores numéricos do atributo. Se o atributo foi descoberto automaticamente, os valores numéricos dele são retornados como um intervalo [min. máx.). Se você requisitar explicitamente um atributo numérico com um ou mais intervalos na consulta, a lista conterá um intervalo fechado-aberto [início fim) para cada intervalo.

A lista de valores de atributo pode não incluir todos os valores encontrados nos documentos, uma vez que as opções de consulta determinam quantos documentos examinar e quantos valores retornar.

As informações agregadas de cada atributo podem ser lidas nos resultados da pesquisa:

Results<ScoredDocument> results = index.search(...);
for (FacetResult facetInfo : results.getFacets()) {
  ...
}

Por exemplo, documentos que incluam um atributo "tamanho" com valores de string e valores numéricos podem ser retornados pela consulta. O FacetResult desse atributo será criado assim:

FacetResult.newBuilder()
        .setName("size")
        .addValue(FacetResultValue.create("[8, 10)", 22, refinement_key)
        .addValue(FacetResultValue.create("small", 100, refinement_key)
        .addValue(FacetResultValue.create("medium", 300, refinement_key)
        .addValue(FacetResultValue.create("large", 250, refinement_key).build());

FacetResultValue.label é criado com base em um valor de atributo. Os valores numéricos são retornados como uma representação "stringficada" de um intervalo.

O refinement_key é uma string segura para Web/URL que pode ser usada em uma consulta posterior para recuperar os documentos correspondentes ao nome e ao valor do atributo.

Como usar atributos para refinar/filtrar uma consulta

Query query = Query.newBuilder()
  .addFacetRefinementFromToken(refinement_key1)
  .addFacetRefinementFromToken(refinement_key2)
  .addFacetRefinementFromToken(refinement_key3)
  .build("some_query");

É possível combinar refinamentos para um ou mais atributos na mesma solicitação. Todos os refinamentos pertencentes ao mesmo atributo serão unidos com um OR. Os refinamentos para atributos diferentes serão combinados com AND.

Também é possível criar uma chave FacetRefinement personalizada manualmente. Consulte a documentação da classe para mais informações.