Búsqueda por facetas

La búsqueda por facetas te permite adjuntar información categórica a tus documentos. Una faceta es un par de atributo y valor. Por ejemplo, la faceta llamada “tamaño” podría tener los valores “pequeño”, “mediano” y “grande”.

Mediante el uso de facetas con la búsqueda, puedes recuperar información de resumen a fin de precisar las consultas y “desglosar” los resultados en una serie de pasos.

Esto es útil para aplicaciones tales como los sitios de compras, en los que se quiere ofrecer a los clientes un conjunto de filtros para acotar la selección de productos que desean ver.

Los datos agregados de una faceta muestran cómo se distribuyen sus valores. Por ejemplo, la faceta “tamaño” puede aparecer en muchos de los documentos de tu conjunto de resultados. Los datos agregados de esta faceta podrían mostrar que el valor “pequeño” aparece 100 veces, “mediano” 300 veces y “grande” 250 veces. Cada par de faceta y valor representa un subconjunto de documentos en el resultado de la consulta. A cada par se asocia una clave denominada perfeccionamiento. Puedes incluir perfeccionamientos en una consulta para recuperar documentos que coincidan con la string de consulta y que tengan valores de faceta que se correspondan con uno o más perfeccionamientos.

Cuando llevas a cabo una búsqueda, puedes elegir qué facetas recopilar y mostrar con los resultados, o bien habilitar la detección de facetas para que se seleccionen de forma automática las que aparezcan con mayor frecuencia en tus documentos.

Agregado de facetas a un documento

Agrega facetas a un documento antes de agregar el documento a un índice. Hazlo al mismo tiempo que especificas los campos del 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);
  }
}

La faceta es similar a un campo de un documento; tiene un nombre y recibe un valor.

Los nombres de las facetas siguen las mismas reglas que los campos de los documentos: los nombres distinguen entre mayúscula y minúscula, y solo pueden contener caracteres ASCII. Deben empezar con una letra y pueden incluir letras, dígitos o guiones bajos. El nombre no puede tener más de 500 caracteres.

El valor de una faceta puede ser una string atómica (de no más de 500 caracteres) o un número (valor de punto flotante de doble precisión entre -2,147,483,647 y 2,147,483,647).

Para asignar varios valores a una faceta de un documento, puedes agregar varias veces una faceta del mismo nombre y tipo, pero con un valor diferente en cada caso.

Las facetas pueden tener una cantidad ilimitada de valores. Tampoco hay restricciones en cuanto a la cantidad de facetas que puedes agregar a un documento o la cantidad de facetas con nombre único que puede incluir un índice.

Ten en cuenta que cada vez que uses una faceta, puede recibir un valor atómico o numérico. Es posible agregar una faceta con el nombre “tamaño” a un documento con el valor de string “pequeño” y a otro documento con el valor numérico 8. De hecho, la misma faceta puede aparecer varias veces en el mismo documento con ambos tipos de valores. No recomendamos usar valores atómicos y numéricos para la misma faceta aunque esté permitido.

Si bien cada faceta tiene un tipo específico cuando la agregas a un documento, los resultados de la búsqueda reúnen todos sus valores. Por ejemplo, los resultados para la faceta “tamaño” podrían mostrar que existen 100 instancias del valor “pequeño”, 150 instancias de “mediano” y 135 instancias de valores numéricos en el rango [4, 8). No se muestran los valores numéricos exactos ni su distribución de frecuencia.

Cuando recuperas un documento mediante una consulta, no puedes acceder directamente a sus facetas y valores. Debes solicitar que se muestre la información de la faceta con la consulta, como se explica en la sección siguiente.

Búsqueda por facetas para recuperar información sobre facetas

Puedes pedirle al backend de la búsqueda que detecte las facetas que se usan con más frecuencia. Esto se denomina "detección automática de facetas". También puedes recuperar información de facetas de manera explícita seleccionando una faceta por nombre o por nombre y valor. Puedes combinar las tres clases de recuperación de facetas en una misma consulta.

El pedido de información de las facetas no afectará los documentos que muestre la consulta. Sí puede afectar el rendimiento. Realizar una búsqueda por facetas con la profundidad predeterminada de 1,000 tiene el mismo efecto que configurar el límite del marcador de opciones de ordenamiento en 1,000.

Detección automática de facetas

La detección automática de facetas busca las facetas que aparecen con mayor frecuencia en la totalidad de tus documentos. Por ejemplo, imagina que los documentos que coinciden con tu consulta incluyen una faceta “color” que aparece 5 veces con el valor “rojo”, 5 veces con el valor “blanco” y 5 veces con el color “azul”. El recuento total de la faceta es 15. Para los fines de detección, se clasificaría en una posición superior a la de otra faceta “tono” que apareciera en los mismos documentos 6 veces con el valor “oscuro” y 7 veces con el valor “claro”.

Para habilitar la detección de facetas, debes configurarla en la consulta:

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());
        }
      }
  }
}

Cuando recuperas facetas con la detección, de forma predeterminada, solo se muestran los 10 valores que aparecen con mayor frecuencia para una faceta. Puedes aumentar este límite hasta 100 con FacetOptions.Builder.setDiscoveryValueLimit().

Ten en cuenta que la detección automática de facetas no tiene por objeto mostrar todas las facetas posibles y sus valores. Las facetas que se muestran a partir de la detección pueden variar entre distintas ejecuciones. Si deseas consultar por un conjunto fijo de facetas, usa un parámetro return_facets en la consulta.

Los valores string se mostrarán de manera individual. Los valores numéricos de una faceta detectada se muestran en un único rango [mín. máx.). Puedes analizar ese rango y crear un subrango más pequeño para una consulta posterior.

Selección de facetas por nombre

Para recuperar información acerca de una faceta solo por su nombre, agrega un objeto ReturnFacet en la consulta y especifica el nombre de la faceta:

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());
        }
      }
  }
}

Cuando recuperas facetas por el nombre, de manera predeterminada, solo se muestran los 10 valores que aparecen con mayor frecuencia para una faceta. Puedes aumentar este límite hasta 20 con FacetOptions.Builder.setDiscoveryValueLimit().

Selección de facetas por nombre y valor

Para recuperar una faceta con un valor particular, agrega un objeto ReturnFacet que incluya una 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());
            }
        }
    }
}

Todos los valores de un FacetRequest deben ser del mismo tipo, ya sea una lista de valores string o, para números, una lista de FacetRanges, que son intervalos cerrados a la izquierda (comienzo) y abiertos a la derecha (final). Si tu faceta posee una combinación de valores numéricos y string, agrega distintas solicitudes FacetRequests para cada uno.

Opciones

Puedes controlar la búsqueda por facetas agregando un parámetro FacetOptions a una llamada de consulta. Este parámetro toma una instancia única de FacetOptions. Usa este parámetro para anular el comportamiento predeterminado de la búsqueda por facetas.

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 Descripción Valor predeterminado
DiscoveryLimit Cantidad de facetas a descubrir si la detección de facetas está activada. Si el valor es 0, la detección de facetas se inhabilitará. 10
DiscoveryValueLimit Cantidad de valores que se muestran para cada una de las facetas descubiertas más importantes. 10
Depth La cantidad mínima de documentos en los resultados de la consulta a evaluarse para recopilar información de la faceta. 1,000

La opción Depth se aplica a los tres tipos de agregación de facetas: por nombre, por nombre y valor, y autodetección. Las otras opciones son solo para la autodetección.

Ten en cuenta que la profundidad de las facetas suele mucho mayor que el límite de la consulta. Los resultados de las facetas se calculan como mínimo para el valor de profundidad de los documentos. Si configuraste el límite del marcador de opciones de ordenamiento en un valor superior al de la profundidad, se usará el límite del marcador.

Recuperación de resultados de facetas

Cuando usas parámetros de búsqueda por facetas en una consulta, la agregación de la información de las facetas se muestra junto con el resultado de la consulta.

La consulta tendrá una lista de FacetResult. Habrá un resultado en la lista por cada faceta que apareció en un documento que coincidió con tu consulta. Para cada resultado, obtendrás lo siguiente:

  • El nombre de la faceta.
  • Una lista de los valores más frecuentes de la faceta. A cada valor le corresponde un conteo de la cantidad de veces que apareció y una clave de perfeccionamiento que se puede usar con el propósito de recuperar los documentos que coinciden con esta consulta y con el valor de la faceta.

Ten en cuenta que la lista de valores incluirá los valores numéricos y las strings de la faceta. Si la faceta se detectó de forma automática, sus valores numéricos se mostrarán como un único intervalo [mín., máx.). Si en la consulta solicitaste de forma explícita una faceta numérica con uno o más rangos, la lista mostrará un intervalo cerrado-abierto [inicio, fin) por cada rango.

La lista de valores de la faceta puede no incluir todos los valores encontrados en los documentos, ya que las opciones de la consulta determinan cuántos documentos se examinan y cuántos valores se muestran.

La agregación de la información de cada faceta puede leerse desde los resultados de la búsqueda:

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

Por ejemplo, una consulta podría encontrar documentos que incluyan una faceta “tamaño” con valores string y numéricos. Los FacetResult de esta faceta tendrían el siguiente formato:

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 se construye con un valor de faceta. Los valores numéricos se muestran como una representación “en forma de string” de un rango.

refinement_key es una string segura para la Web y URL que puede usarse en una consulta posterior a fin de recuperar los documentos que coincidan con el nombre y valor de faceta de ese resultado.

Uso de facetas para precisar o filtrar una consulta

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

Puedes combinar perfeccionamientos para una o más facetas diferentes en la misma solicitud. Todos los perfeccionamientos que pertenezcan a la misma faceta se unirán con un operador OR. Los perfeccionamientos de facetas diferentes se combinarán con AND.

También se puede crear una clave FacetRefinement personalizada de forma manual. Consulta la documentación sobre clases para obtener más información.