分面搜索

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

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

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

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

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

在文档中添加构面

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

package app

import (
    "appengine"
    "appengine/search"
    "net/http"
)

type ComputerDoc struct {
    Name      search.Atom
    Type      search.Atom `search:",facet"`
    RAMSizeGB float64     `search:",facet"`
}

func handlePut(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    index, err := search.Open("products")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    _, err = index.Put(c, "doc1", &ComputerDoc{
        Name:      "x86",
        Type:      "computer",
        RAMSizeGB: 8.0,
    })
    // Handle err and write HTTP response.
}

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

构面名称和文档字段遵循相同的规则:名称区分大小写,而且只能包含 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 次)。

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

func handleSearch(w http.ResponseWriter, r *http.Request) {
    index, err := search.Open("products")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    it := index.Search(c, "name:x86", &search.SearchOptions{
        Facets: {
            search.AutoFacetDiscovery(0, 0),
        },
    })
    facets, err := it.Facets()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    for _, results := range facets {
        for i, facet := range result {
            // The facet results are grouped by facet name.
            // Print the name of each group before its values.
            if i == 0 {
                fmt.Fprintf(w, "Facet %s:\n", facet.Name)
            }
            fmt.Fprintf(w, "    %v: count=%d", facet.Value, facet.Count)
        }
    }
}

通过发现来检索构面时,默认情况下,系统只会返回构面最常出现的 10 个值。您可以使用 AutoFacetDiscovery 的第一个参数最高将此限制增加到 100。

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

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

按名称选择构面

要仅按构面名称检索构面相关信息,请向查询的搜索选项添加带有构面名称的 FacetDiscovery

it := index.Search(c, "name:x86", &search.SearchOptions{
    Facets: {
        FacetDiscovery("Type"),
        FacetDiscovery("RAMSizeGB"),
    },
})

按名称检索构面时,系统只会返回构面最常出现的 10 个值。

按名称和值选择构面

要仅检索有关构面特定值的信息,请添加带有构面名称和所需值的 FacetDiscovery:

it := index.Search(c, "name:x86", &search.SearchOptions{
    Facets: {
        // Fetch the "Type" facet with Values "computer" and "printer"
        FacetDiscovery("Type",
            search.Atom("computer"),
            search.Atom("printer"),
        ),
        // Fetch the "RAMSizeGB" facet with values in the ranges [0, 4), [4, 8), and [8, max]
        FacetDiscovery("RAMSizeGB",
            search.Range{Start: 0, End: 4},
            search.Range{Start: 4, End: 8},
            search.AtLeast(8),
        ),
    },
})

单个 FacetDiscovery 中的值必须属于相同类型,可以是 search.Atom 值的列表;如果其值为数字,也可以是 search.Range 列表,后者左侧为闭区间(起始值),右侧为开区间(结束值)。如果您的构面既有字符串值又有数值,请为各个值添加单独的 FacetDisovery 选项。

选项

您可以在查询的 SearchOptions 中添加 FacetDocumentDepth 选项,以控制为收集构面信息所需评估文档的最小数量。如果未指定,则此深度默认为 1000。

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

检索构面结果

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

搜索 Iterator 具有方法 Facets,此方法会将汇总构面信息返回为 [][]FacetResult。结果已经过整理,因此与查询匹配的文档中出现的每个构面都具有一份构面结果。 对于每个结果,您都会获得以下信息:

  • 构面名称
  • 来自最常见值列表的一个构面值
  • 该值出现的近似次数

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

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

每个构面的汇总信息都可以通过迭代器读取:

it := index.Search(...)
facets, err := it.Facets()  // And check err != nil.
for _, results := range facets {
    for _, facet := range results {
        ...
    }
}

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

[][]search.FacetResult{
    {
        {Name: "size", Value: search.Range{Start: 8, End: 10}, Count: 22},
        {Name: "size", Value: search.Atom("small"), Count: 100},
        {Name: "size", Value: search.Atom("medium"), Count: 300},
        {Name: "size", Value: search.Atom("large"), Count: 250},
    },
}

使用构面优化/过滤查询

每个 FacetResult 都可用于进一步缩小结果范围,以仅包含具有这些构面值的文档。要使用其中一个或多个键优化查询,请将查询作为搜索选项传递:

it := index.Search(c, "...", &search.SearchOptions{
    Refinements: []search.Facet{
        facetResult1.Facet,
        facetResult2.Facet,
    },
})

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

您还可以手动创建自定义的 Facet 进行优化。如需了解详情,请参阅相应的参考文档。