前言
Elasticsearch的搜索返回结果默认是以相关性排序,但是在实际业务中,可能需要按照热度排序,甚至需要按照多字段排序。
排序
Elasticsearch 中,相关性得分由一个浮点数进行表示,并在搜索结果中通过_score参数返回,默认排序是_score降序。
简单排序
如果我们的搜索需要将最新的tweets排在最前。 我们可以使用sort参数进行实现:
GET /_search
{
"query" : {
"bool" : {
"filter" : { "term" : { "status" : 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 ]
},
...
}
注意:
- 排序默认是升序排序,升序的话可以省略后面的desc参数。
- _score为null,因为它并没有用于排序。
- date 字段的值表示为自 epoch (January 1, 1970 00:00:00 UTC)以来的毫秒数,通过 sort 字段的值进行返回。
多级排序
很多时候,我们的排序都是需要多级的。如下,先按照date的值逆序排序,date相同的按_score逆序排序。(_score是搜索的匹配度)
GET /_search
{
"query" : {
"bool" : {
"must": { "match": { "tweet": "manage text search" }},
"filter" : { "term" : { "user_id" : 2 }}
}
},
"sort": [
{ "date": { "order": "desc" }},
{ "_score": { "order": "desc" }}
]
}
注意:
简单搜索也是支持多级排序的。
GET /_search?sort=date:desc&sort=_score&q=search
多值字段排序
在Elasticsearch中有数组类型的数据,这些数据没有固有顺序,在Elasticsearch中我们可以将多值字段减为单值来进行排序处理(具体方法有min 、 max 、 avg 或是 sum)。
"sort": {
"dates": {
"order": "asc",
"mode": "min"
}
}
字符串排序
被解析的字符串字段也是多值字段, 但是很少会按照你想要的方式进行排序。如果你想分析一个字符串,如 fine old art , 这包含 3 项。我们很可能想要按第一项的字母排序,然后按第二项的字母排序,诸如此类,但是 Elasticsearch 在排序过程中没有这样的信息。
我们需要以整个not_analyzed字符串来进行排序,但是not_analyzed无法进行全文搜索,所以我们需要一个not_analyzed来进行排序,一个analyzed进行全文搜索。
原映射:
"tweet": {
"type": "string",
"analyzer": "english"
}
新映射:
"tweet": {
"type": "string",
"analyzer": "english",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
}
}
在tweet中添加一个元素raw,raw使用not_analyzed。这样我们就可以用tweet搜索,用tweet.raw排序:
GET /_search
{
"query": {
"match": {
"tweet": "elasticsearch"
}
},
"sort": "tweet.raw"
}
注意:全文 analyzed 字段排序会消耗大量的内存。
相关性
Elasticsearch搜索的返回结果默认是按照相关性(_score值)逆序排序的。什么是相关性呢?其实就是一个结果与搜索词的匹配度。那么匹配度的具体算法是怎样的呢?
搜索时Elasticsearch会给每一个文档生成一个_score字段,这个字段的值就是相关性。而这个值的计算方式取决与查询类型与查询结果。fuzzy 查询会计算与关键词的拼写相似程度,terms 查询会计算 找到的内容与关键词组成部分匹配的百分比,但是通常我们说的相关性relevance是我们用来计算全文本字段的值相对于全文本检索词相似程度的算法。
Elasticsearch的相似度算法被定义为检索词频率(TF)/反向文档频率(IDF)它包括:
- 检索词频率:检索词在该字段出现的频率?出现频率越高,相关性也越高。
- 反向文档频率:每个检索词在索引中出现的频率?频率越高,相关性越低。检索词出现在多数文档中会比出现在少数文档中的权重更低。
- 字段长度准则:字段的长度是多少?长度越长,相关性越低。检索词出现在一个短的title要比同样的词出现在一个长的content字段权重更大。
注意:bool查询,每个查询子句计算得出的评分会被合并到总的相关性评分中。
相关性标准
如果我们想要知道搜索对于文档相关性的计算情况的话,可以使用explain参数。
GET /index_name/_search?explain=true
{
"query": {
"match" : {
"name" : "对马岛之魂"
}
}
}
返回结果:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 24.250643,
"hits" : [
{
"_shard" : "[gp_gamegroupdata_v1][0]",
"_node" : "6dhOZW6-TVq4d7YBj_fRcg",
"_index" : "gp_gamegroupdata_v1",
"_type" : "_doc",
"_id" : "11",
"_score" : 24.250643,
"_source" : {
"tags" : [
"gamegroup"
],
"update_time" : 1607156354,
"update_name" : "白茶",
"circle_log" : "http://vgn-images.oss-cn-beijing.aliyuncs.com/groupCircleLogo_16030750487403.png",
"create_name" : "白茶",
"@timestamp" : "2021-02-18T03:40:01.235Z",
"id" : 11,
"is_top" : 0,
"rank_num" : 19,
"product_id" : 0,
"post_num" : 0,
"name" : "对马岛之魂",
"today_post_num" : 0,
"category" : "",
"status" : 0,
"log" : "http://vgn-images.oss-cn-beijing.aliyuncs.com/groupLogo_16030750468590.png",
"create_time" : 1603073095,
"@version" : "1",
"tab_id" : ""
},
"_explanation" : {
"value" : 24.250643,
"description" : "sum of:",
"details" : [
{
"value" : 6.0626607,
"description" : "weight(name:对 in 13) [PerFieldSimilarity], result of:",
"details" : [
{
"value" : 6.0626607,
"description" : "score(freq=1.0), computed as boost * idf * tf from:",
"details" : [
{
"value" : 2.2,
"description" : "boost",
"details" : [ ]
},
{
"value" : 6.088289,
"description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
"details" : [
{
"value" : 1,
"description" : "n, number of documents containing term",
"details" : [ ]
},
{
"value" : 660,
"description" : "N, total number of documents with field",
"details" : [ ]
}
]
},
{
"value" : 0.45263207,
"description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:",
"details" : [
{
"value" : 1.0,
"description" : "freq, occurrences of term within document",
"details" : [ ]
},
{
"value" : 1.2,
"description" : "k1, term saturation parameter",
"details" : [ ]
},
{
"value" : 0.75,
"description" : "b, length normalization parameter",
"details" : [ ]
},
{
"value" : 4.0,
"description" : "dl, length of field",
"details" : [ ]
},
{
"value" : 3.959091,
"description" : "avgdl, average length of field",
"details" : [ ]
}
]
}
]
}
]
},
...
]
}
}
]
}
}
注意:较低版本的话可以不添加=true。
这里返回结果暂时不做分析。