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 個文件符合預設根網域。十二個文件具有 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

在分散式搜尋期間,從分片內部請求的 bucket 數量超出 limit 的數量。

當個別分片具有非常不同的熱門詞彙時,較大的值可以提高最終傳回的「熱門詞彙」的準確性。

預設值 -1 會根據指定的其他選項套用啟發式方法。

refine

如果為 true,則開啟分散式 facet 精煉。這會使用第二階段,從其初始內部結果中未包含這些 bucket 的分片中檢索最終結果所需的任何 bucket,以便每個分片都對此 facet 和任何子 facet 中傳回的每個 bucket 做出貢獻。這使得傳回的 bucket 的計數和統計資料精確。

overrefine

在分散式搜尋期間,確定要精煉哪些 bucket 時,內部要考慮的 bucket 數量超出 limit 的數量。

當個別分片具有非常不同的熱門詞彙時,較大的值可以提高最終傳回的「熱門詞彙」的準確性,並且目前的 sort 選項可能會導致精煉將詞彙推到排序列表的較下方 (例如:sort:"count asc")

預設值 -1 會根據指定的其他選項套用啟發式方法。

mincount

僅傳回計數至少為此數字的 bucket。預設值為 1

missing

一個布林值,指定是否應傳回特殊的「missing」bucket,該 bucket 由欄位中沒有值的文件定義。預設值為 false

numBuckets

一個布林值。如果為 true,則將「numBuckets」加入回應,這是一個整數,表示 facet 的 bucket 數量 (與傳回的 bucket 數量相對)。預設值為 false

allBuckets

一個布林值。如果為 true,則將「allBuckets」bucket 加入回應,表示所有 bucket 的聯集。對於多值欄位,這與網域中所有文件的 bucket 不同,因為單一文件可以屬於多個 bucket。預設值為 false

prefix

僅針對以指定前置詞開頭的詞彙產生 bucket。

facet

將為每個傳回的 bucket 計算的聚合、指標或巢狀 facet

method

此參數指示要使用的 facet 演算法

  • dv DocValues,收集到序數陣列中

  • uif UnInvertedField,收集到序數陣列中

  • dvhash DocValues,收集到雜湊中 - 提高高基數欄位的效率

  • enum TermsEnum 然後相交 DocSet (可串流)

  • stream 目前等同於 enum。用於已索引的非點欄位,且排序為 index asc,並停用 allBucketsnumBucketsmissing

  • smart 為欄位類型選擇最佳方法 (這是預設值)

prelim_sort

一個可選參數,用於指定在初始收集熱門 bucket 期間使用的最終 sort 的近似值,當 sort 參數非常昂貴時

查詢 Facet

查詢 facet 會產生一個單一 bucket,其中包含符合網域以及指定查詢的文件。

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

使用者也可以指定子 facet (「分組」facet 或指標)

  • 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
}

範圍 Facet

範圍 facet 會在日期或數值欄位上產生多個 bucket。

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

上面範圍 facet 的輸出看起來會有點像

"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 參數

範圍 facet 參數名稱和語意在很大程度上反映了 facet.range 查詢參數樣式的 facet。例如,此處的「start」對應於 facet.range 命令中的「facet.range.start」。

參數 描述

field

要從中產生範圍 bucket 的數值欄位或日期欄位。

start

範圍的下限。

end

範圍的上限。

gap

產生的每個範圍 bucket 的大小。

hardend

一個布林值,如果為 true,則表示最後一個 bucket 將以「end」結尾,即使其寬度小於「gap」。如果為 false,則最後一個 bucket 的寬度將為「gap」,這可能會超出「end」。

other

此參數表示,除了 startend 之間每個範圍限制的計數之外,還應計算以下項目的計數…

  • 「before」所有欄位值低於第一個範圍下限的記錄

  • 「after」所有欄位值大於最後一個範圍上限的記錄

  • 「between」所有欄位值介於所有範圍的起始和結束界限之間的記錄

  • 「none」不計算此資訊

  • 「all」before、between 和 after 的快捷方式

include

依預設,用於計算 startend 之間範圍 facet 的範圍包含其下限,但不包含上限。「before」範圍不包含,「after」範圍包含。此預設值 (等同於下方的「lower」) 不會導致邊界的雙重計數。include 參數可以是下列選項的任何組合

  • 「lower」所有基於 gap 的範圍都包含其下限

  • 「upper」所有基於 gap 的範圍都包含其上限

  • 「edge」第一個和最後一個 gap 範圍都包含其邊界 (即,第一個的下限,最後一個的上限),即使未指定對應的 upper/lower 選項也是如此

  • 「outer」即使第一個或最後一個範圍已包含這些界限,「before」和「after」範圍也將包含其界限。

  • 「all」lower、upper、edge 和 outer 的簡寫

facet

將為每個傳回的 bucket 計算的聚合、指標或巢狀 facet

ranges

指定時,任意範圍列表會計算給定範圍而非 startgapend 的 facet。使用 startendgap 時,範圍或 bucket 的寬度始終是固定的。如果需要在不同的範圍寬度上計算範圍 facet,則應指定 ranges

  • 不允許同時指定 startendgap 以及 ranges,且請求會失敗。

  • 當在範圍 facet 中指定 ranges 時,將會忽略 hardendincludeother 參數。

請參閱 任意範圍

任意範圍

任意範圍由 from 和 to 值組成,在這些值上計算範圍 bucket。此範圍可以用兩種語法指定。

參數 描述

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 實現相同的行為。可以使用 inclusive_toinclusive_from 的組合來實現 lowerupperouteredge

具有 ranges 的範圍 facet

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)"
        }
      ]
    }
  }
}'

上面範圍 facet 的輸出看起來會有點像

{
  "prices": {
    "buckets": [
      {
        "val": "[0,20)",
        "count": 5
      },
      {
        "val": "[40,100)",
        "count": 2
      }
    ]
  }
}
指定 range 時,請求中的值將用作回應中的索引鍵。在其他情況下,索引鍵是使用 fromtoinclusive_toinclusive_from 產生的。目前,不支援自訂 key

熱圖 Facet

heatmap facet 會產生一個 2D 網格,其中包含在每個網格單元格中具有空間資料的文件的 facet 計數。

此功能主要記錄在參考指南的空間部分中。關鍵參數是 type 以指定 heatmap,以及 field 以指示空間 RPT 欄位。其餘的參數名稱使用相同的名稱和語意,反映了 facet.heatmap 查詢參數樣式的 facet,儘管沒有 "facet.heatmap." 前置詞。例如,此處的 geom 對應於 facet.heatmap 命令中的 facet.heatmap.geom

與將網域分割成 bucket 的其他 facet 不同,heatmap facet 目前不支援巢狀 Facet
  • 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));

而 facet 回應將如下所示

{
  "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]]
    }
  }
}

統計 Facet 函數

與到目前為止討論的所有 facet 不同,聚合函數 (也稱為facet 函數分析函數指標) 不會將資料分割成 bucket。相反地,它們會計算網域中所有文件的內容。

聚合 範例 描述

sum

sum(銷售額)

數值的總和

avg

avg(人氣)

數值的平均值

min

min(薪資)

最小值

max

max(mul(價格,人氣))

最大值

missing

missing(作者)

對於給定欄位或函數沒有值的文件數量

countvals

countvals(作者)

給定欄位或函數的值數量

unique

unique(作者)

給定欄位的唯一值數量。超過 100 個值時,會產生不精確的估計

uniqueBlock

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

與上述相同,但佔用空間較小,嚴格用於計算區塊聯結區塊的數量。給定的欄位在區塊中必須是唯一的,且僅支援單值字串欄位,建議使用 docValues。

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

與上述相同,但使用給定查詢的位元組集來聚合命中次數。

hll

hll(作者)

透過超記錄記錄演算法分散式基數估計

percentile

percentile(薪資,50,75,99,99.9)

透過 t-digest 演算法的百分位數估計值。依此指標排序時,會使用列出的第一個百分位數作為排序值。

sumsq

sumsq(租金)

欄位或函數的平方總和

variance

variance(租金)

數值欄位或函數的變異數

stddev

stddev(租金)

欄位或函數的標準差

relatedness

relatedness('人氣:[100 TO *]','有庫存:true')

一個函數,用於計算網域中文件相對於背景集的前景集 (兩者都定義為查詢) 的相關性分數。這主要用於建置語意知識圖譜

諸如 avg 的數值聚合函數可以是任何數值欄位,也可以是多個數值欄位的巢狀函數,例如 avg(div(人氣,價格))

要求聚合函數最常見的方法是使用包含您希望計算的運算式的簡單字串

  • 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 指令添加到父分面的分面指令區塊中。從技術上講,每個分面指令實際上都是一個子分面,因為我們從一個由主查詢和過濾器定義領域的單一分面桶開始。

巢狀分面範例

我們先從一個簡單的非巢狀詞項分面(針對類別欄位 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}]}}]}}

依巢狀函數排序分面

欄位或詞項分面的預設排序方式是依桶計數降序排列。我們可以選擇依每個桶中出現的任何分面函數,以升序或降序方式進行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() 和區塊聯接計數

當集合包含巢狀文件時,在搜尋父文件並希望針對所有受影響的子文件(反之亦然)計算統計數據時,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(…​) 統計函數允許根據前景和背景文件集對文件集進行評分,目的是找到構成「語義知識圖」的特定關係。

從本質上講,語義知識圖利用倒排索引和互補的反向索引來表示節點(詞項)和邊(多個詞項/節點的相交 postings list 中的文件)。這在每對節點及其對應的邊之間提供了一個間接層,使邊可以從底層語料庫統計數據中動態實現。因此,任何節點組合都可以與任何其他節點的邊實體化並進行評分,以揭示節點之間的潛在關係。

— Grainger 等人。
語義知識圖

relatedness(…​) 函數用於相對於在函數參數中指定為查詢的「前景」和「背景」文件集對這些關係進行「評分」。

與大多數聚合函數不同,relatedness(…​) 函數知道它是否以及如何在巢狀分面中使用。它獨立於其父/祖先桶評估定義當前桶的查詢,並將這些文檔與「前景集」相交,前景集由前景查詢與祖先桶組合定義。然後將結果與針對「背景集」(完全由背景查詢定義)完成的類似交集進行比較,以查看當前桶與前景集之間是否存在相對於背景集的正相關或負相關。

目前未定義 allBuckets 上下文中 relatedness(…​) 的語義。因此,儘管可以為同時指定 allBuckets:true 的分面請求指定 relatedness(…​) 統計資料,但 allBuckets 桶本身不包含相關性計算。
雖然將背景集定義為 *:* 或前景查詢的其他超集非常常見,但並非嚴格要求。relatedness(…​) 函數可以用於比較多組文檔與正交前景/背景查詢的統計相關性。

relatedness() 選項

當使用擴展的 type:func 語法來指定 relatedness() 聚合時,可以使用可選的 min_popularity (浮點數) 選項來指定 foreground_popularitybackground_popularity 值的下限,必須滿足該下限,相關性分數才是有效的——如果未滿足此 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