1. 最佳字段

 假设有个网站允许用户搜索博客的内容,以下面两篇博客内容文档为例:

PUT /my_index/my_type/1
{
    "title": "Quick brown rabbits",
    "body":  "Brown rabbits are commonly seen."
}

PUT /my_index/my_type/2
{
    "title": "Keeping pets healthy",
    "body":  "My quick brown fox eats rabbits on a regular basis."
}

此时用户搜索 " brown fox ",用肉眼判断文档二更匹配。 由于不知道该搜索词出现的字段,所以我们用 bool 查询进行查询。

{
    "query": {
        "bool": {
            "should": [
                { "match": { "title": "Brown fox" }},
                { "match": { "body":  "Brown fox" }}
            ]
        }
    }
}

但在返回的结果中文档 1 比 文档 2 的相关度高,因为搜索时,会将每个字段的相关度相加然后计算总评分,文档一的 title 和 body 中都包含 Brown, 所以评分较高,如果不是将每个字段的评分想加,而是将最佳匹配字段的评分作为查询的整体评分,返回的结果将是同时包含 brown 和 fox 的字段所在文档相关度比较高。

  此时应该使用 dis_max 查询,而不是 bool 查询。最大化查询(Disjunction Max Query)指的是: 将任何与查询匹配的文档作为结果返回,但是每个文档的评分都是以最佳匹配的评分作为结果 ,而不是再进行计算。意思是该文档的评分是dis_max下所有查询的评分的最大值,不再进行求和平均计算。

{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Brown fox" }},
                { "match": { "body":  "Brown fox" }}
            ]
        }
    }
}

由于dis_max会忽略其他匹配查询的分数,可以通过 tie_breaker进行使得其他匹配的分数也参与到计算该文档的评分中。和权重boost不同,权重是字段所占的权重,而tie_breaker是查询所占的权重。

表示除了最佳匹配,次匹配所占总分比例的 30 %
{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Quick pets" }},
                { "match": { "body":  "Quick pets" }}
            ],
            "tie_breaker": 0.3
        }
    }
}

2. 多数字段

  全文搜索被称作是 召回率(Recall) 与 精确率(Precision) 的战场: 召回率 ——返回所有的相关文档; 精确率 ——不返回无关文档。目的是在结果的第一页中为用户呈现最为相关的文档。

  为了提高召回率的效果,我们扩大搜索范围——不仅返回与用户搜索词精确匹配的文档,还会返回我们认为与查询相关的所有文档。如果一个用户搜索 “quick brown box” ,一个包含词语 fast foxes 的文档被认为是非常合理的返回结果。如果有多个文档比该文档更匹配,则该文档出现的位置应该在这些文档之后。

  提高全文相关性精度的常用方式是为同一文本建立多种方式的索引,每种方式都提供了一个不同的相关度信号 signal 。主字段会以尽可能多的形式的去匹配尽可能多的文档。比如我们搜索华为手机,在手机的 desc 字段使用默认分词器,而他的词根 ' 华为手机 ' 不分词。在搜索华为手机时,会将该文档作为结果返回,而他的词根用来提高该文档的相关度。

  对我们的字段索引两次:一次使用词干模式以及一次非词干模式。为了做到这点,采用 multifields 来实现。

PUT /my_index
{
    "settings": { "number_of_shards": 1 }, 
    "mappings": {
        "my_type": {
            "properties": {
                "title": { 
                    "type":     "string",
                    "analyzer": "english",
                    "fields": {
                        "std":   { 
                            "type":     "string",
                            "analyzer": "standard"
                        }
                    }
                }
            }
        }
    }
}

 上例中,给某个字段索引了两次,分别使用了不同的分词器,可以使用广度匹配字段用来匹配更多的数据,用来提升召回率,然后用该字段的词根来将相关度更高的文档置于顶部。

GET /index/_search
{
   "query": {
        "multi_match": {
            "query":       "jumping rabbits",
            "type":        "most_fields",
            "fields":      [ "title", "title.std^10" ] 
        }
    }
}

跨字段实体搜索,比如人、地址等实体,需要用多个字段来唯一表示一个实体,(last_name、first_name),使用bool查询将会使代码过长,而是用多字段查询又不能完全符合题意。因为多字段搜索是为多数字段是否满足查询条件,不能在所有字段中找到最匹配的、搜索词在多个字段值的出现的频率不一样,会导致结果有误差。

  一种解决方案是增加一个新的字段,比如full_name, 可以使用该字段进行对复杂实体的搜索,但是又会出现冗余数据。es给我们提供了两种解决方案,一个是在索引时,一个是在搜索时。

3. 混合字段

在之前说过, all_filed字段包括了该文档所有值的结合,但是这样并不灵活,我们可以通过copy_to参数人为增加一个all字段,比如下列增加一个full_name字段。

PUT /my_index
{
    "mappings": {
        "person": {
            "properties": {
                "first_name": {
                    "type":     "string",
                    "copy_to":  "full_name" 
                },
                "last_name": {
                    "type":     "string",
                    "copy_to":  "full_name" 
                },
                "full_name": {
                    "type":     "string"
                }
            }
        }
    }
}

在索引时创建_all字段是一个方案,而es还在搜索时提供了另一种方案,使用 cross_fields 类型进行 multi_match 查询。 cross_fields 使用词中心式(term-centric)的查询方式,这与 best_fields 和 most_fields 使用字段中心式(field-centric)的查询方式非常不同。
  字段中心式:搜索词必须同时出现在同一个字段中。
  词中心式:搜索词必须同时出现,但可以在任意一个字段中。

GET /books/_search
{
    "query": {
        "multi_match": {
            "query":       "peter smith",
            "type":        "cross_fields", 
            "operator":    "and",
            "fields":      [ "first_name", "last_name" ]
        }
    }
}