分面搜索

通过分面搜索,您可以向文档附加分类信息。构面是指属性/值对。例如,名为“大小”的构面可能有“小”“中”和“大”三个值。

通过将构面与搜索结合使用,您可以检索摘要信息,通过一系列步骤帮助您优化查询并深入分析结果。

这对购物网站等应用非常有用,这些应用往往会为客户提供一组过滤条件,用于缩小他们想要查看产品的范围。

构面的汇总数据显示了构面值的分布情况。例如,“大小”构面可能会出现在结果集的许多文档中。该构面的汇总数据可能显示,“小”值出现 100 次,“中”值出现 300 次,“大”值出现 250 次。每个构面/值对都代表查询结果中的一个文档子集。每个对都关联了一个名为“refinement”(优化)的键。您可以在查询中加入优化键,以检索与查询字符串相匹配且其构面值与一个或多个优化键相对应的文档。

您在执行搜索时,可以选择要收集并与结果一起显示哪些构面,也可以启用构面发现功能以自动选择文档中最常出现的构面。

在文档中添加构面

在将文档添加到索引之前,请在文档中添加构面。执行此操作的同时,您可以指定文档的字段:

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

构面类似于文档字段;它有名称并且有一个值。

构面名称和文档字段遵循相同的规则:名称区分大小写,而且只能包含 ASCII 字符。名称必须以字母开头,可以包含字母、数字或下划线。名称的长度不能超过 500 个字符。

构面的值可以是原子字符串(不超过 500 个字符),也可以是数字(介于 -2,147,483,647 和 2,147,483,647 之间的双精度浮点值)。

您可以多次添加具有相同名称和类型的构面,且每次使用不同的值,从而为一个文档中的一个构面分配多个值。

构面的值没有数量限制。您可以添加到文档的构面数量或索引中具有独一无二名称的构面数量也没有限制。

请注意,每次您使用构面时,都可以选择为其采用原子字符串或数值。名为“大小”的构面可以在附加到一个文档时采用字符串值“小”,而在附加到另一个文档时采用数值 8。实际上,同一个构面可以在同一个文档中出现多次,并且同时采用这两类值。不过,虽然您可以这样做,但我们不建议对同一个构面同时使用原子字符串和数值。

虽然在您将构面添加到文档时,该构面具有特定类型的值,但搜索结果会将其所有值收集到一起。例如,构面“大小”的结果可能会显示,“小”值的实例有 100 个,“中”值的实例有 150 个,[4, 8) 范围内的数值的实例有 135 个。系统不会显示确切的数值和其频率分布。

使用查询检索文档时,无法直接访问其构面和值。如下一部分所述,您必须要求系统随查询一起返回构面信息。

使用分面搜索来检索构面信息

您可以要求搜索后端为您发现最常用的构面,这称为自动构面发现。您还可以按名称或者同时按名称和值来选择构面,从而明确检索构面信息。您可以在单个查询中混搭使用这三种构面检索方式。

请求构面信息不会影响查询返回的文档,但可能会影响性能。执行采用默认深度 (1000) 的分面搜索和将排序选项记分程序限制设置为 1000 具有相同的效果。

自动构面发现

自动构面发现功能用于查找所有相关文档中最常出现的构面。例如,假设与您的查询相匹配的文档包括“颜色”构面,其中“红色”值出现 5 次,“白色”值出现 5 次,“蓝色”值出现 5 次。该构面的总计数为 15。就发现而言,它的排名高于另一个构面“颜色深浅”(在同一个匹配的文档中,“深”值出现 6 次,“浅”值出现 7 次)。

您必须在查询中进行相关设置才能启用构面发现:

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

通过发现来检索构面时,默认情况下,系统只会返回构面最常出现的 10 个值。您可以使用 FacetOptions.Builder.setDiscoveryValueLimit() 将此限制提高到 100。

请注意,自动构面发现并不意味着返回所有可能的构面及其值。每次运行发现时返回的构面可能不同。如果希望获得一组固定的构面,请在您的查询中使用 return_facets 参数。

字符串值将单独返回。已发现构面的数值在单个范围 [最小值, 最大值) 中返回。您可以检查该范围,并为之后的查询创建更小的子范围。

按名称选择构面

如需仅按照构面名称来检索构面的相关信息,请将 ReturnFacet 对象添加到您的查询,指定构面名称:

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

按名称检索构面时,默认只会返回 10 个最常出现的构面值。您可以使用 FacetOptions.Builder.setDiscoveryValueLimit() 将此限制提高到 20。

按名称和值选择构面

如需检索带有特定值的构面,请添加包含 FacetRequestReturnFacet 对象:

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

单个 FacetRequest 中的值都必须属于相同类型,既可以都是字符串值列表中的值,也可以都是 FacetRanges 的数字列表中的值,其中左侧为闭区间(起始值),右侧为开区间(结束值)。如果您的构面既有字符串值又有数值,请为各个值添加单独的 FacetRequest。

选项

您可以通过将 FacetOptions 参数添加到查询调用来控制分面搜索。 此参数接受 FacetOptions 的单个实例。使用此参数替换分面搜索的默认行为。

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");
参数 说明 默认值
DiscoveryLimit 启用构面发现时要发现的构面数。如果为 0,则会停用构面发现。 10
DiscoveryValueLimit 要为每个最常发现的构面返回的值数量。 10
Depth 查询结果中要进行评估以收集构面信息的最小文档数。 1000

Depth 选项对所有三种类型的构面汇总均适用:按名称、名称和值以及自动发现。 其他选项仅适用于自动发现。

请注意,构面深度通常大大超过查询限制。计算出的构面结果至少为文档的深度数。如果您设置的排序选项评分限制超过深度,则将改用该评分限制。

检索构面结果

当您在查询中使用分面搜索参数时,汇总构面信息会随查询结果一同提供。

查询将包含一个 FacetResult 列表。 与查询相匹配的文档中出现的每个构面都在该列表中有一个结果。对于每个结果,您都会获得以下信息:

  • 构面名称
  • 构面最常出现的值的列表。对于每个值,系统都会显示出现次数的近似计数,以及可用于检索与此查询和构面值匹配的文档的优化键。

请注意,值列表包含构面的字符串和数值。如果构面是自动发现的,则其数值会以单个区间 [最小值, 最大值) 形式返回。如果您在查询中明确要求使用一个或多个范围返回数字构面,则列表会为每个范围包含一个半闭半开区间 [起始值, 结束值)。

构面值的列表可能不含在您文档中找到的所有值,因为查询选项决定了要检查的文档数量和要返回的值数量。

搜索结果中可读取每个构面的汇总信息:

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

例如,查询可能已找到包含带有字符串值和数值的“大小”构面的文档。此构面的 FacetResult 构成如下:

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 通过构面值构造而成。数值作为范围的“字符串化”表示法返回。

refinement_key 是网络/网址安全字符串,可以在以后的查询中用于检索与该结果的构面名称和值相匹配的文档。

使用构面优化/过滤查询

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

您可以在同一请求中结合一个或多个不同构面的优化键。属于同一个构面的所有优化键通过 OR 连接。不同构面的优化键通过 AND 组合。

也可以手动创建自定义 FacetRefinement 键。如需了解详情,请参阅类文档。