排序與相關性
默認情況下,返回的結果是按照 相關性 進行排序的——最相關的文檔排在最前。
在 Elasticsearch 中, 相關性得分 由一個浮點數進行表示,并在搜索結果中通過 _score 參數返回, 默認排序是 _score 降序。
有時,相關性評分對你來說并沒有意義。例如,下面的查詢返回所有 user_id 字段包含 1 的結果:
GET /_search
{
"query" : {
"bool" : {
"filter" : {
"term" : {
"user_id" : 1
}
}
}
}
}
這里沒有一個有意義的分數:因為我們使用的是 filter (過濾),這表明我們只希望獲取匹配 user_id: 1 的文檔,并沒有試圖確定這些文檔的相關性。 實際上文檔將按照隨機順序返回,并且每個文檔都會評為零分。
按照字段的值排序
在這個案例中,通過時間來對 tweets 進行排序是有意義的,最新的 tweets 排在最前。 我們可以使用 sort 參數進行實現:
GET /_search
{
"query" : {
"bool" : {
"filter" : { "term" : { "user_id" : 1 }}
}
},
"sort": { "date": { "order": "desc" }}
}
你會注意到結果中的兩個不同點:
"hits" : {
"total" : 6,
"max_score" : null,
"hits" : [ {
"_index" : "us",
"_type" : "tweet",
"_id" : "14",
"_score" : null,
"_source" : {
"date": "2014-09-24",
...
},
"sort" : [ 1411516800000 ]
},
...
}
- _score 不被計算, 因為它并沒有用于排序。
- date 字段的值表示為自 epoch (January 1, 1970 00:00:00 UTC)以來的毫秒數,通過 sort 字段的值進行返回。
首先我們在每個結果中有一個新的名為 sort 的元素,它包含了我們用于排序的值。 在這個案例中,我們按照 date 進行排序,在內部被索引為 自 epoch 以來的毫秒數 。 long 類型數 1411516800000 等價于日期字符串 2014-09-24 00:00:00 UTC 。
其次 _score 和 max_score 字段都是 null 。計算 _score 的花銷巨大,通常僅用于排序; 我們并不根據相關性排序,所以記錄 _score 是沒有意義的。如果無論如何你都要計算 _score , 你可以將 track_scores 參數設置為 true 。
多級排序
假定我們想要結合使用 date 和 _score 進行查詢,并且匹配的結果首先按照日期排序,然后按照相關性排序:
GET /_search
{
"query" : {
"bool" : {
"must": { "match": { "tweet": "manage text search" }},
"filter" : { "term" : { "user_id" : 2 }}
}
},
"sort": [
{ "date": { "order": "desc" }},
{ "_score": { "order": "desc" }}
]
}
排序條件的順序是很重要的。結果首先按第一個條件排序,僅當結果集的第一個 sort 值完全相同時才會按照第二個條件進行排序,以此類推。
多級排序并不一定包含 _score 。你可以根據一些不同的字段進行排序,如地理距離或是腳本計算的特定值。
多值字段的排序
一種情形是字段有多個值的排序, 需要記住這些值并沒有固有的順序;一個多值的字段僅僅是多個值的包裝,這時應該選擇哪個進行排序呢?
對于數字或日期,你可以將多值字段減為單值,這可以通過使用 min 、 max 、 avg 或是 sum 排序模式 。 例如你可以按照每個 date 字段中的最早日期進行排序,通過以下方法:
"sort": {
"dates": {
"order": "asc",
"mode": "min"
}
}
字符串排序與多字段
被解析的字符串字段也是多值字段, 但是很少會按照你想要的方式進行排序。如果你想分析一個字符串,如 fine old art , 這包含 3 項。我們很可能想要按第一項的字母排序,然后按第二項的字母排序,諸如此類,但是 Elasticsearch 在排序過程中沒有這樣的信息。
你可以使用 min 和 max 排序模式(默認是 min ),但是這會導致排序以 art 或是 old ,任何一個都不是所希望的。
為了以字符串字段進行排序,這個字段應僅包含一項: 整個 not_analyzed 字符串。 但是我們仍需要 analyzed 字段,這樣才能以全文進行查詢
一個簡單的方法是用兩種方式對同一個字符串進行索引,這將在文檔中包括兩個字段: analyzed 用于搜索, not_analyzed 用于排序
但是保存相同的字符串兩次在 _source 字段是浪費空間的。 我們真正想要做的是傳遞一個 單字段 但是卻用兩種方式索引它。所有的 _core_field 類型 (strings, numbers, Booleans, dates) 接收一個 fields 參數
該參數允許你轉化一個簡單的映射如:
"tweet": {
"type": "string",
"analyzer": "english"
}
為一個多字段映射如:
"tweet": {
"type": "string",
"analyzer": "english",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
}
}
現在,至少只要我們重新索引了我們的數據,使用 tweet 字段用于搜索,tweet.raw 字段用于排序:
GET /_search
{
"query": {
"match": {
"tweet": "elasticsearch"
}
},
"sort": "tweet.raw"
}
執行分布式檢索
查詢滿足條件的前N項,在協調服務器進行判定需要取回哪些數據,并從分片獲取并返回。
Bouncing Results
想象一下有兩個文檔有同樣值的時間戳字段,搜索結果用 timestamp 字段來排序。 由于搜索請求是在所有有效的分片副本間輪詢的,那就有可能發生主分片處理請求時,這兩個文檔是一種順序, 而副本分片處理請求時又是另一種順序。
這就是所謂的 bouncing results 問題: 每次用戶刷新頁面,搜索結果表現是不同的順序。 讓同一個用戶始終使用同一個分片,這樣可以避免這種問題, 可以設置 preference 參數為一個特定的任意值比如用戶會話ID來解決。