JSON 分面 API

JSON 分面和分析

JSON 分面公开了与 Solr 传统分面类似的功能,但更强调可用性。它比传统分面有几个优点

  • 更易于以编程方式构建复杂或嵌套的分面

  • JSON 提供的嵌套和结构使分面比传统分面 API 的扁平命名空间更易于阅读和理解。

  • 对指标和分析的一流支持

  • 更标准化的响应格式使客户端更容易解析和使用响应

分面搜索是关于聚合数据并计算有关该数据的指标。

主要有两种类型的分面

  • 将数据(域)划分为多个的分面,用于分区或分类

  • 计算给定桶的数据的分面(通常是指标、统计信息或分析函数)

桶分面示例

这是一个桶分面的示例,它根据 cat 字段(类别的缩写)将文档划分为桶,并返回前 3 个桶

  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/techproducts/query -d '
{
  "query": "*:*",
  "facet": {
    "categories" : {
      "type": "terms",
      "field": "cat",
      "limit": 3
    }
  }
}'
final TermsFacetMap categoryFacet = new TermsFacetMap("cat").setLimit(3);
final JsonQueryRequest request =
    new JsonQueryRequest().setQuery("*:*").withFacet("categories", categoryFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);

以下响应显示有 32 个文档与默认根域匹配。12 个文档具有 cat:electronics,4 个文档具有 cat:currency,等等。

[...]
  "facets":{
    "count":32,
    "categories":{
      "buckets":[{
          "val":"electronics",
          "count":12},
        {
          "val":"currency",
          "count":4},
        {
          "val":"memory",
          "count":3},
      ]
    }
  }

统计分面示例

统计(也称为 聚合分析)分面对于显示从查询结果中派生的信息(除了结果本身)非常有用。例如,统计分面可用于向在电子商务网站上寻找内存的用户提供上下文。下面的示例计算平均价格(和其他统计信息),并允许用户衡量其购物车中的内存条是否价格合理。

  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/techproducts/query -d '
q=memory&
fq=inStock:true&
json.facet={
  "avg_price" : "avg(price)",
  "num_suppliers" : "unique(manu_exact)",
  "median_weight" : "percentile(weight,50)"
}'
final JsonQueryRequest request =
    new JsonQueryRequest()
        .setQuery("memory")
        .withFilter("inStock:true")
        .withStatFacet("avg_price", "avg(price)")
        .withStatFacet("min_manufacturedate_dt", "min(manufacturedate_dt)")
        .withStatFacet("num_suppliers", "unique(manu_exact)")
        .withStatFacet("median_weight", "percentile(weight,50)");
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);

上述分面请求的响应将以匹配根域的文档开始(包含 "memory" 且 inStock:true 的文档),然后是 facets 块中的请求统计信息

 "facets" : {
    "count" : 4,
    "avg_price" : 109.9950008392334,
    "num_suppliers" : 3,
    "median_weight" : 352.0
  }

分面类型

有 4 种不同类型的桶分面,它们以两种不同的方式工作

  • "terms" 和 "range" 分面产生多个桶,并将域中的每个文档分配到这些桶中的一个(或多个)

  • "query" 和 "heatmap" 分面总是产生一个单个桶,域中的所有文档都属于该桶

以下详细介绍了这些分面类型。

词项分面

terms 分面根据字段中的唯一值对域进行分桶。

  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/techproducts/query -d '
{
  "query": "*:*",
  "facet": {
    categories:{
      "type": "terms",
      "field" : "cat",
      "limit" : 5
    }
  }
}'
final TermsFacetMap categoryFacet = new TermsFacetMap("cat").setLimit(5);
final JsonQueryRequest request =
    new JsonQueryRequest().setQuery("*:*").withFacet("categories", categoryFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
参数 描述

field

用于分面的字段名称。

offset

用于分页,跳过前 N 个桶。默认为 0。

limit

限制返回的桶的数量。默认为 10。

sort

指定如何对生成的桶进行排序。

count 指定文档计数,index 按桶值的索引(自然)顺序排序。 也可以按桶中出现的任何分面函数/统计信息排序。 默认值为 count desc。 此参数也可以在 JSON 中指定,例如 sort:{count:desc}。 排序顺序可以是“asc”或“desc”

overrequest

在分布式搜索期间,从分片内部请求的超出 limit 的桶的数量。

当单个分片具有非常不同的前几个术语时,较大的值可以提高返回的最终“前几个术语”的准确性。

默认值 -1 会根据指定的其他选项应用启发式方法。

refine

如果为 true,则开启分布式分面细化。 这将使用第二阶段来检索最终结果所需的任何桶,这些桶来自最初内部结果中未包含这些桶的分片,以便每个分片都对此分面和任何子分面中的每个返回的桶做出贡献。 这使得返回的桶的计数和统计信息是准确的。

overrefine

在分布式搜索期间,确定要细化的桶时,要内部考虑的超出 limit 的桶的数量。

当单个分片具有非常不同的前几个术语,并且当前 sort 选项可能导致细化将术语推到排序列表的较低位置(例如:sort:"count asc")时,较大的值可以提高返回的最终“前几个术语”的准确性。

默认值 -1 会根据指定的其他选项应用启发式方法。

mincount

仅返回计数至少为此数字的桶。默认为 1

missing

一个布尔值,指定是否应返回一个特殊的“缺失”桶,该桶由字段中没有值的文档定义。默认为 false

numBuckets

一个布尔值。 如果为 true,则向响应添加“numBuckets”,一个表示分面的桶数的整数(与返回的桶数相对)。默认为 false

allBuckets

一个布尔值。如果为 true,则向响应添加“allBuckets”桶,表示所有桶的并集。 对于多值字段,这与域中所有文档的桶不同,因为单个文档可以属于多个桶。默认为 false

prefix

仅为以指定前缀开头的术语生成桶。

facet

将为每个返回的桶计算的聚合、度量或嵌套分面

method

此参数指示要使用的分面算法

  • dv DocValues,收集到序号数组中

  • uif UnInvertedField,收集到序号数组中

  • dvhash DocValues,收集到哈希中 - 提高了高基数字段的效率

  • enum TermsEnum,然后相交 DocSet(可流式传输)

  • stream 目前等效于 enum。 用于索引的、非点字段,具有排序 index asc,并禁用 allBucketsnumBucketsmissing

  • smart 为字段类型选择最佳方法(这是默认值)

prelim_sort

一个可选参数,用于指定在初始收集前几个桶期间使用的最终 sort 的近似值,当sort 参数非常昂贵时

查询分面

查询分面生成与域以及指定查询匹配的单个文档桶。

  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/techproducts/query -d '
{
  "query": "*:*",
  "facet": {
    "high_popularity": {
      "type": "query",
      "q": "popularity:[8 TO 10]"
    }
  }
}'
QueryFacetMap queryFacet = new QueryFacetMap("popularity:[8 TO 10]");
final JsonQueryRequest request =
    new JsonQueryRequest().setQuery("*:*").withFacet("high_popularity", queryFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);

用户还可以指定子分面(“分桶”分面或度量)

  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/techproducts/query -d '
{
  "query": "*:*",
  "facet": {
    "high_popularity": {
      "type": "query",
      "q": "popularity:[8 TO 10]",
      "facet" : {
        "average_price" : "avg(price)"
      }
    }
  }
}'
QueryFacetMap queryFacet =
    new QueryFacetMap("popularity:[8 TO 10]").withStatSubFacet("average_price", "avg(price)");
final JsonQueryRequest request =
    new JsonQueryRequest().setQuery("*:*").withFacet("high_popularity", queryFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);

响应示例

"high_popularity" : {
  "count" : 36,
  "average_price" : 36.75
}

范围分面

范围分面在日期或数字字段上生成多个桶。

  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/techproducts/query -d '
{
  "query": "*:*",
  "facet": {
    "prices": {
      "type": "range",
      "field": "price",
      "start": 0,
      "end": 100,
      "gap": 20
    }
  }
}'
RangeFacetMap rangeFacet = new RangeFacetMap("price", 0.0, 100.0, 20.0);
final JsonQueryRequest request =
    new JsonQueryRequest().setQuery("*:*").withFacet("prices", rangeFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);

上面的范围分面的输出看起来有点像

"prices":{
  "buckets":[
    {
      "val":0.0,  // the bucket value represents the start of each range.  This bucket covers 0-20
      "count":5},
    {
      "val":20.0,
      "count":0},
    {
      "val":40.0,
      "count":0},
    {
      "val":60.0,
      "count":1},
    {
      "val":80.0,
      "count":1}
  ]
}

范围分面参数

范围分面参数名称和语义在很大程度上反映了 facet.range 查询参数样式的分面。 例如,此处的“start”对应于 facet.range 命令中的“facet.range.start”。

参数 描述

field

用于生成范围桶的数字字段或日期字段。

start

范围的下限。

end

范围的上限。

gap

生成的每个范围桶的大小。

hardend

一个布尔值,如果为 true,则表示最后一个桶将以“end”结尾,即使它小于“gap”的宽度。 如果为 false,则最后一个桶将为“gap”的宽度,这可能会延伸超过“end”。

other

此参数指示除了计算 startend 之间每个范围约束的计数之外,还应计算以下内容的计数……

  • “before” 所有字段值低于第一个范围下限的记录

  • “after” 所有字段值大于最后一个范围上限的记录

  • “between” 所有字段值在所有范围的开始和结束边界之间的记录

  • “none” 不计算此信息

  • “all” before、between 和 after 的快捷方式

include

默认情况下,用于计算 startend 之间范围分面的范围包括其下限,但不包括上限。“before”范围是独占的,“after”范围是包含的。 此默认值,等效于下面的“lower”,不会导致边界处的重复计数。 include 参数可以是以下选项的任意组合

  • “lower” 所有基于 gap 的范围都包括其下限

  • “upper” 所有基于 gap 的范围都包括其上限

  • “edge” 第一个和最后一个 gap 范围包括其边缘边界(即,第一个范围的下限,最后一个范围的上限),即使未指定相应的 upper/lower 选项

  • “outer” “before”和“after”范围将包含其边界,即使第一个或最后一个范围已经包含这些边界。

  • “all” lower、upper、edge、outer 的简写

facet

将为每个返回的桶计算的聚合、度量或嵌套分面

ranges

当指定时,任意范围列表会在给定范围而不是 startgapend 上计算分面。 对于 startendgap,范围或桶的宽度始终是固定的。 如果需要在变化的范围宽度上计算范围分面,则应指定 ranges

  • 不允许同时指定 startendgap 以及 ranges,并且请求将失败。

  • 当在范围分面中指定 ranges 时,将忽略 hardendincludeother 参数。

参考任意范围

任意范围

任意范围由计算范围桶的 from 和 to 值组成。 此范围可以使用两种语法指定。

参数 描述

from

范围的下限。 如果未指定,则默认为 *

to

范围的上限。 如果未指定,则默认为 *

inclusive_from

一个布尔值,如果为 true,则表示包含下限 from。 这默认为 true

inclusive_to

一个布尔值,如果为 true,则表示包含上限 to。 这默认为 false

range

范围指定为字符串。 这在语义上类似于 facet.interval

  • 当指定 range 时,将忽略该范围中的所有上述参数 fromto

  • range 始终以 ([ 开头,并以 )] 结尾

    • ( - 排除下限

    • [ - 包含下限

    • ) - 排除上限

    • ] - 包含上限

例如,对于范围 (5,10],5 被排除,而 10 被包含

other 与范围

当指定 ranges 时,将忽略 other 参数,但是可以通过 ranges 实现相同的行为。

  • before - 这等效于 [*,some_val) 或仅指定 to

  • after - 这等效于 (som_val, *] 或仅指定 from

  • between - 这等效于将 startend 分别指定为 fromto

include 与范围

当指定 ranges 时,将忽略 include 参数,但是可以通过 ranges 实现相同的行为。 lowerupperouteredge 都可以使用 inclusive_toinclusive_from 的组合来实现。

带有 ranges 的范围分面

curl https://127.0.0.1:8983/solr/techproducts/query -d '
{
  "query": "*:*",
  "facet": {
    "prices": {
      "type": "range",
      "field": "price",
      "ranges": [
        {
          "from": 0,
          "to": 20,
          "inclusive_from": true,
          "inclusive_to": false
        },
        {
          "range": "[40,100)"
        }
      ]
    }
  }
}'

上面的范围分面的输出看起来有点像

{
  "prices": {
    "buckets": [
      {
        "val": "[0,20)",
        "count": 5
      },
      {
        "val": "[40,100)",
        "count": 2
      }
    ]
  }
}
当指定 range 时,请求中的值将用作响应中的键。 在其他情况下,键是使用 fromtoinclusive_toinclusive_from 生成的。 当前,不支持自定义 key

热图分面

heatmap 分面为在每个网格单元中具有空间数据的文档生成分面计数的 2D 网格。

此功能主要记录在参考指南的空间部分中。 关键参数是 type 以指定 heatmapfield 以指示空间 RPT 字段。 其余的参数名称使用相同的名称和语义,反映了 facet.heatmap 查询参数样式的分面,但没有“facet.heatmap.”前缀。 例如,此处的 geom 对应于 facet.heatmap 命令中的 facet.heatmap.geom

与将域划分为桶的其他分面不同,heatmap 分面当前不支持嵌套分面
  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/spatialdata/query -d '
{
  "query": "*:*",
  "facet": {
    "locations": {
      "type": "heatmap",
      "field": "location_srpt",
      "geom": "[\"50 20\" TO \"180 90\"]",
      "gridLevel": 4
    }
  }
}'
final JsonQueryRequest request =
    new JsonQueryRequest()
        .setQuery("*:*")
        .setLimit(0)
        .withFacet(
            "locations",
            new HeatmapFacetMap("location_srpt")
                .setHeatmapFormat(HeatmapFacetMap.HeatmapFormat.INTS2D)
                .setRegionQuery("[\"50 20\" TO \"180 90\"]")
                .setGridLevel(4));

分面响应将如下所示

{
  "facets": {
    "locations":{
      "gridLevel":1,
      "columns":6,
      "rows":4,
      "minX":-180.0,
      "maxX":90.0,
      "minY":-90.0,
      "maxY":90.0,
      "counts_ints2D":[[68,1270,459,5359,39456,1713],[123,10472,13620,7777,18376,6239],[88,6,3898,989,1314,255],[0,0,30,1,0,1]]
    }
  }
}

统计分面函数

与到目前为止讨论的所有分面不同,聚合函数(也称为分面函数分析函数度量)不会将数据划分为桶。 相反,它们会计算域中所有文档的值。

聚合 示例 描述

sum

sum(sales)

数值的总和

avg

avg(popularity)

数值的平均值

min

min(salary)

最小值

max

max(mul(price,popularity))

最大值

missing

missing(author)

给定字段或函数没有值的文档数量

countvals

countvals(author)

给定字段或函数的值的数量

unique

unique(author)

给定字段的唯一值的数量。超过 100 个值时,结果不是精确估计

uniqueBlock

uniqueBlock(_root_)uniqueBlock($fldref),其中 fldref=_root_

与上述相同,但占用空间更小,专用于计算 Block Join 块的数量。给定字段在块之间必须是唯一的,并且只支持单值字符串字段,建议使用 docValues。

uniqueBlock({!v=type:parent})uniqueBlock({!v=$qryref}),其中 qryref=type:parent

与上述相同,但使用给定查询的位集来聚合命中。

hll

hll(author)

通过 hyper-log-log 算法进行分布式基数估计

percentile

percentile(salary,50,75,99,99.9)

通过 t-digest 算法进行百分位数估计。当按此指标排序时,将使用列出的第一个百分位数作为排序值。

sumsq

sumsq(rent)

字段或函数的平方和

variance

variance(rent)

数值字段或函数的方差

stddev

stddev(rent)

字段或函数的标准差

relatedness

relatedness('popularity:[100 TO *]','inStock:true')

一个用于计算域中与前景集合相关的文档的相关性得分的函数,相对于背景集合(两者都定义为查询)。这主要用于构建语义知识图谱

诸如 avg 之类的数值聚合函数可以应用于任何数值字段,或者应用于多个数值字段的嵌套函数,例如 avg(div(popularity,price))

请求聚合函数最常见的方式是使用一个简单的字符串,其中包含你想要计算的表达式

  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/techproducts/query -d '
{
  "query": "*:*",
  "filter": [
    "price:[1.0 TO *]",
    "popularity:[0 TO 10]"
  ],
  "facet": {
    "avg_value": "avg(div(popularity,price))"
  }
}'
final JsonQueryRequest request =
    new JsonQueryRequest()
        .setQuery("*:*")
        .withFilter("price:[1.0 TO *]")
        .withFilter("popularity:[0 TO 10]")
        .withStatFacet("min_manu_id_s", "min(manu_id_s)")
        .withStatFacet("avg_value", "avg(div(popularity,price))");
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);

扩展形式允许指定本地参数。这些参数可以被某些专用聚合(如relatedness())显式使用,也可以用作参数引用,使聚合表达式更具可读性,而无需使用(全局)请求参数

  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/techproducts/query -d '
{
  "query": "*:*",
  "filter": [
    "price:[1.0 TO *]",
    "popularity:[0 TO 10]"
  ],
  "facet": {
    "avg_value" : {
      "type": "func",
      "func": "avg(div($numer,$denom))",
      "numer": "mul(popularity,3.0)",
      "denom": "price"
    }
  }
}'
final Map<String, Object> expandedStatFacet = new HashMap<>();
expandedStatFacet.put("type", "func");
expandedStatFacet.put("func", "avg(div($numer,$denom))");
expandedStatFacet.put("numer", "mul(popularity,3.0)");
expandedStatFacet.put("denom", "price");
final JsonQueryRequest request =
    new JsonQueryRequest()
        .setQuery("*:*")
        .withFilter("price:[1.0 TO *]")
        .withFilter("popularity:[0 TO 10]")
        .withFacet("avg_value", expandedStatFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);

嵌套分面

嵌套分面,或子分面,允许在任何将域划分为桶的分面命令(例如 termsrangequery)下嵌套分面命令。然后,这些子分面针对其父分面的每个桶中所有文档定义的域进行评估。

语法与顶层分面相同 - 只需将 facet 命令添加到父分面的分面命令块中。从技术上讲,每个分面命令实际上都是一个子分面,因为我们从一个由主查询和过滤器定义的域的单个分面桶开始。

嵌套分面示例

让我们从一个简单的非嵌套 terms 分面开始,该分面位于类别字段 cat

  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/techproducts/query -d '
{
  "query": "*:*",
  "facet": {
    "categories": {
      "type": "terms",
      "field": "cat",
      "limit": 3
    }
  }
}'
final TermsFacetMap categoryFacet = new TermsFacetMap("cat").setLimit(3);
final JsonQueryRequest request =
    new JsonQueryRequest().setQuery("*:*").withFacet("categories", categoryFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);

上面分面的响应将显示顶级类别以及属于每个类别桶的文档数量。嵌套分面可用于收集有关每个文档桶的附加信息。例如,使用下面的嵌套分面,我们可以找到顶级类别以及每个类别中的领先制造商

  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/techproducts/query -d '
{
  "query": "*:*",
  "facet": {
    "categories": {
      "type": "terms",
      "field": "cat",
      "limit": 3,
      "facet": {
        "top_manufacturer": {
          "type": "terms",
          "field": "manu_id_s",
          "limit": 1
        }
      }
    }
  }
}'
final TermsFacetMap topCategoriesFacet = new TermsFacetMap("cat").setLimit(3);
final TermsFacetMap topManufacturerFacet = new TermsFacetMap("manu_id_s").setLimit(1);
topCategoriesFacet.withSubFacet("top_manufacturers", topManufacturerFacet);
final JsonQueryRequest request =
    new JsonQueryRequest().setQuery("*:*").withFacet("categories", topCategoriesFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);

响应将如下所示

"facets":{
    "count":32,
    "categories":{
      "buckets":[{
          "val":"electronics",
          "count":12,
          "top_manufacturer":{
            "buckets":[{
                "val":"corsair",
                "count":3}]}},
        {
          "val":"currency",
          "count":4,
          "top_manufacturer":{
            "buckets":[{
                "val":"boa",
                "count":1}]}}]}}

按嵌套函数对分面进行排序

字段或 terms 分面的默认排序是按桶计数降序排列。我们可以选择按每个桶中出现的任何分面函数进行升序或降序 sort

  • curl

  • SolrJ

curl https://127.0.0.1:8983/solr/techproducts/query -d '
{
  "query": "*:*",
  "facet": {
    "categories":{
      "type" : "terms",     // terms facet creates a bucket for each indexed term in the field
      "field" : "cat",
      "limit": 3,
      "sort" : "avg_price desc",
      "facet" : {
        "avg_price" : "avg(price)",
      }
    }
  }
}'
final TermsFacetMap topCategoriesFacet =
    new TermsFacetMap("cat")
        .setLimit(3)
        .withStatSubFacet("avg_price", "avg(price)")
        .setSort("avg_price desc");
final JsonQueryRequest request =
    new JsonQueryRequest().setQuery("*:*").withFacet("categories", topCategoriesFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);

在某些情况下,所需的 sort 可能是对于每个桶计算成本都很高的聚合函数。可以使用 prelim_sort 选项来指定 sort 的近似值,用于初始对桶进行排名,以确定顶级候选者(基于 limitoverrequest)。只有在优化了顶级候选桶之后,才会使用实际的 sort

{
  "categories": {
    "type" : "terms",
    "field" : "cat",
    "refine": true,
    "limit": 10,
    "overrequest": 100,
    "prelim_sort": "sales_rank desc",
    "sort": "prod_quality desc",
    "facet": {
      "prod_quality": "avg(div(prod(rating,sales_rank),prod(num_returns,price)))"
      "sales_rank": "sum(sales_rank)"
    }
  }
}

更改域

如上所述,分面基于其文档“域”计算桶或统计信息。

  • 默认情况下,顶层分面使用与主查询匹配的所有文档集作为其域。

  • 使用包含该桶中所有文档的域,为父分面的每个桶计算嵌套“子分面”。

除了此默认行为之外,还可以扩大、缩小或完全更改域。JSON 分面 API 支持通过其 domain 属性修改域。有关更多详细信息,请参阅JSON 分面域更改

特殊统计分面函数

大多数统计分面函数(avgsumsq 等)允许用户对文档组执行数学计算。但是,一些函数涉及更多,需要单独解释。这些在下面的部分中进行了更详细的描述。

uniqueBlock() 和 Block Join 计数

当一个集合包含嵌套文档时,当搜索父文档并且你想要针对所有受影响的子文档计算统计信息时(反之亦然),blockChildrenblockParent 域更改非常有用。但是,如果你只需要知道当前域中存在的所有块的计数,则更有效的选项是 uniqueBlock() 聚合函数。

假设我们有多个 SKU 的产品,并且我们想计算每种颜色的产品数量。

{
  "id": "1", "type": "product", "name": "Solr T-Shirt",
  "_childDocuments_": [
    { "id": "11", "type": "SKU", "color": "Red",  "size": "L" },
    { "id": "12", "type": "SKU", "color": "Blue", "size": "L" },
    { "id": "13", "type": "SKU", "color": "Red",  "size": "M" }
  ]
},
{
  "id": "2", "type": "product", "name": "Solr T-Shirt",
  "_childDocuments_": [
    { "id": "21", "type": "SKU", "color": "Blue", "size": "S" }
  ]
}

当针对一组 SKU 文档进行搜索时,我们可以请求按颜色进行分面,并使用嵌套的统计信息来计算所有“块” - 又名:产品

"color": {
  "type": "terms",
  "field": "color",
  "limit": -1,
  "facet": {
    "productsCount": "uniqueBlock(_root_)"
      // or "uniqueBlock({!v=type:product})"
  }
}

并得到

"color": {
   "buckets": [
      { "val": "Blue", "count": 2, "productsCount": 2 },
      { "val": "Red", "count": 2, "productsCount": 1 }
   ]
}

请注意,_root_ 是 Lucene 添加到每个子文档的内部字段,以引用父文档。聚合 uniqueBlock(_root_) 在功能上等同于 unique(_root_),但针对嵌套文档块结构进行了优化。建议为 uniqueBlock 计算定义 limit: -1,如上例所示,因为 limit 参数的默认值为 10,而 uniqueBlock 在使用 -1 时应该更快。

relatedness() 和语义知识图谱

relatedness(…​) 统计函数允许对文档集进行评分,该评分相对于前景和背景文档集,目的是查找构成“语义知识图谱”的临时关系

在其核心,语义知识图谱利用倒排索引以及补充的非倒排索引来表示节点(术语)和边(多个术语/节点的交叉帖子列表中的文档)。这在每对节点及其对应的边之间提供了一个间接层,从而能够从底层语料库统计数据中动态实现边。因此,任何节点组合都可以与任何其他节点实现边,并通过评分来揭示节点之间的潜在关系。

— Grainger et al.
语义知识图谱

relatedness(…​) 函数用于相对于“前景”和“背景”文档集(在函数参数中指定为查询)对这些关系进行“评分”。

与大多数聚合函数不同,relatedness(…​) 函数知道它是否以及如何在嵌套分面中使用。它独立于其父/祖先桶评估定义当前桶的查询,并将这些文档与由前景查询结合祖先桶定义的“前景集”相交。然后将结果与针对“背景集”(完全由背景查询定义)完成的类似相交进行比较,以查看当前桶与前景集之间是否存在正相关或负相关,相对于背景集。

relatedness(…​)allBuckets 上下文中的语义当前未定义。因此,尽管可以为还指定 allBuckets:true 的分面请求指定 relatedness(…​) 统计信息,但 allBuckets 桶本身将不包含相关性计算。
虽然通常将背景集定义为 *:*,或者前景查询的某个超集,但这不是严格要求的。relatedness(…​) 函数可用于比较文档集与正交前景/背景查询的统计相关性。

relatedness() 选项

当使用扩展的 type:func 语法指定 relatedness() 聚合时,可以使用可选的 min_popularity (浮点数) 选项来指定 foreground_popularitybackground_popularity 值的下限,为了使 relatedness 得分有效,必须满足该下限 - 如果未满足此 min_popularity,则 relatedness 得分将为 -Infinity

用于计算 relatedness() 域相关性的默认实现取决于要计算的分面类型。通过有选择地检索每个与桶关联的查询的 DocSet(查询 filterCache)并计算 DocSet 与“前景”和“背景”集的交集,按每个术语计算通用域相关性。对于术语分面(尤其是高基数字段),此方法可能会导致 filterCache 抖动;因此,在可能的情况下,术语分面的 relatedness() 默认使用一种方法,该方法在一次扫描中直接收集所有多个域中的分面计数(从不触及 filterCache)。可以通过将扩展的 type:func 语法 sweep_collection 选项设置为 true(默认)或 false(禁用扫描收集)来显式控制此“单次扫描”收集。

如果 filterCache 足够大,可以容纳相关字段中每个值的条目,而不会为预期的使用模式引起抖动,则禁用低基数字段的 relatedness() 统计信息的扫描收集可能会带来性能优势。一个合理的启发式方法是,基数小于 1,000 的字段可能会从禁用扫描中受益。此启发式方法用于确定默认行为,尤其是因为非扫描收集很容易导致 filterCache 抖动,从而产生系统范围的不利影响。
{
  "type": "func",
  "func": "relatedness($fore,$back)",
  "min_popularity": 0.001
}

当对不相交的前景和背景查询使用 relatedness() 降序排序时,这尤其有用,以确保“顶级桶”都与这两个集合相关。

通过添加 prelim_sort: "count desc" 选项,可以更快地处理对 relatedness(…​) 进行排序的请求。增加 overrequest 可以帮助提高顶级桶的准确性。

语义知识图谱示例

示例文档
curl -sS -X POST 'https://127.0.0.1:8983/solr/gettingstarted/update?commit=true' -d '[
{"id":"01",age:15,"state":"AZ","hobbies":["soccer","painting","cycling"]},
{"id":"02",age:22,"state":"AZ","hobbies":["swimming","darts","cycling"]},
{"id":"03",age:27,"state":"AZ","hobbies":["swimming","frisbee","painting"]},
{"id":"04",age:33,"state":"AZ","hobbies":["darts"]},
{"id":"05",age:42,"state":"AZ","hobbies":["swimming","golf","painting"]},
{"id":"06",age:54,"state":"AZ","hobbies":["swimming","golf"]},
{"id":"07",age:67,"state":"AZ","hobbies":["golf","painting"]},
{"id":"08",age:71,"state":"AZ","hobbies":["painting"]},
{"id":"09",age:14,"state":"CO","hobbies":["soccer","frisbee","skiing","swimming","skating"]},
{"id":"10",age:23,"state":"CO","hobbies":["skiing","darts","cycling","swimming"]},
{"id":"11",age:26,"state":"CO","hobbies":["skiing","golf"]},
{"id":"12",age:35,"state":"CO","hobbies":["golf","frisbee","painting","skiing"]},
{"id":"13",age:47,"state":"CO","hobbies":["skiing","darts","painting","skating"]},
{"id":"14",age:51,"state":"CO","hobbies":["skiing","golf"]},
{"id":"15",age:64,"state":"CO","hobbies":["skating","cycling"]},
{"id":"16",age:73,"state":"CO","hobbies":["painting"]},
]'
示例查询
curl -sS -X POST https://127.0.0.1:8983/solr/gettingstarted/query -d 'rows=0&q=*:*
&back=*:*                                  (1)
&fore=age:[35 TO *]                        (2)
&json.facet={
  hobby : {
    type : terms,
    field : hobbies,
    limit : 5,
    sort : { r1: desc },                   (3)
    facet : {
      r1 : "relatedness($fore,$back)",     (4)
      location : {
        type : terms,
        field : state,
        limit : 2,
        sort : { r2: desc },               (3)
        facet : {
          r2 : "relatedness($fore,$back)"  (4)
        }
      }
    }
  }
}'
1 使用整个集合作为我们的“背景集”
2 使用查询条件 “age >= 35” 来定义我们(初始的)“前景集合”。
3 对于顶层 hobbies 分面和 state 上的子分面,我们都将按照 relatedness(…​) 值进行排序。
4 在两次调用 relatedness(…​) 函数时,我们使用参数变量来引用之前定义的 foreback 查询。
分面响应
"facets":{
  "count":16,
  "hobby":{
    "buckets":[{
        "val":"golf",
        "count":6,                                (1)
        "r1":{
          "relatedness":0.01225,
          "foreground_popularity":0.3125,         (2)
          "background_popularity":0.375},         (3)
        "location":{
          "buckets":[{
              "val":"az",
              "count":3,
              "r2":{
                "relatedness":0.00496,            (4)
                "foreground_popularity":0.1875,   (6)
                "background_popularity":0.5}},    (7)
            {
              "val":"co",
              "count":3,
              "r2":{
                "relatedness":-0.00496,           (5)
                "foreground_popularity":0.125,
                "background_popularity":0.5}}]}},
      {
        "val":"painting",
        "count":8,                                (1)
        "r1":{
          "relatedness":0.01097,
          "foreground_popularity":0.375,
          "background_popularity":0.5},
        "location":{
          "buckets":[{
            ...
1 即使 hobbies:golf 的总分面 count 低于 hobbies:painting,但它的 relatedness 得分更高,表明相对于背景集合(整个集合),高尔夫与我们的前景集合(35 岁以上的人)的相关性比绘画更强。
2 匹配 age:[35 TO *] hobbies:golf 的文档数量占背景集合中总文档数量的 31.25%。
3 背景集合中 37.5% 的文档匹配 hobbies:golf
4 与背景集合相比,亚利桑那州 (AZ) 与嵌套的前景集合(35 岁以上的高尔夫球手)具有正相关关系——即,“与整个国家相比,亚利桑那州的人在统计上更有可能是‘35 岁以上的高尔夫球手’。”
5 科罗拉多州 (CO) 与嵌套的前景集合具有负相关关系——即,“与整个国家相比,科罗拉多州的人在统计上不太可能是‘35 岁以上的高尔夫球手’。”
6 匹配 age:[35 TO *] hobbies:golf state:AZ 的文档数量占背景集合中总文档数量的 18.75%。
7 背景集合中 50% 的文档匹配 state:AZ