Multi_field_Search

多字符串查询

GET /_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title":  "War and Peace" }},
        { "match": { "author": "Leo Tolstoy"   }},
        { "bool":  {
          "should": [
            { "match": { "translator": "Constance Garnett" }},
            { "match": { "translator": "Louise Maude"      }}
          ]
        }}
      ]
    }
  }
}

4个match查询,为什么后两个使用bool包裹?
同一层查询每条语句具有相同的权重!!!

语句的优先级

boost

单字符串查询

用户期望将所有的搜索项堆积到单个字段中。
1、最佳字段
例如我们搜索“es reindex”,对于title和body这样的两个字段,“es reindex”同时出现在一个字段时的文档评分更高
2、多数字段
为了对相关度进行微调,常用的一个技术就是将相同的数据索引到不同的字段,使他们具有独立的分析链。
主字段可能包括它们的词源、同义词以及变音词或口音词等,用来匹配尽可能多的文档。
相同的文本被索引到其他字段,提供更精确的匹配。一个字段可以包括未经词干提取的原词;另一个字段包括口音,还有一个字段提供词语相似性。
其他字段是作为匹配每个文档时提高相关度评分的信号。
3、混合字段
对于某些实体,需要在多个字段中确认其信息,单个字段都只能作为整体的一部分:
Person: first_name、last_name
如同在Persion这个大字段中搜索 first_name和last_name这两个字段

最佳字段

以两个文档为例

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."
}

bool查询,想要匹配Brown fox

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

我们想要的结果是文档2,但是文档1的评分更高
bool如何计算评分:
1、执行should语句中的两个查询。
2、两个查询的评分相加
3、乘以匹配语句的总数
4、除以所有语句总数(这里为2,指的应该两个字段???)
文档1中两个字段都包含brown,所以两个match语句都有一个评分。
文档2中body字段包含两个词,获得较高分,但title字段没有包含任何词,平均一下文档2的整体分就比文档1低。
可以使用dis_max分离最大化查询Disjunction Max Query,大致意思是,将任何与任一查询匹配的文档作为结果返回,但只将最匹配的评分作为查询的评分结果返回:

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

最佳字段查询调优

dis_max查询会单个匹配最佳字段,而忽略其他的匹配。(单个最佳匹配语句的评分作为整体评分)
可以通过指定tie_breaker字段将其他语句的评分也考虑其中。

{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Quick pets" }},
                { "match": { "body":  "Quick pets" }}
            ],
            "tie_breaker": 0.3
        }
    }
}

结果如下:

{
  "hits": [
     {
        "_id": "2",
        "_score": 0.14757764, (1)
        "_source": {
           "title": "Keeping pets healthy",
           "body": "My quick brown fox eats rabbits on a regular basis."
        }
     },
     {
        "_id": "1",
        "_score": 0.124275915, (1)
        "_source": {
           "title": "Quick brown rabbits",
           "body": "Brown rabbits are commonly seen."
        }
     }
   ]
}

不带有tie_breaker参数时,评分1等于评分2
tie_breaker的评分方式:
1、获得最佳匹配语句的评分
2、其他语句的评分结果乘以tie_breaker
3、上述相加即为最后评分
tie_breaker的合理值应该处于0.1-0.4之间

multi_match多匹配查询

在多个字段上反复执行相同的查询。
multi_match多匹配查询的类型有多种,其中三种与前文的最佳字段(best_fields)、多数字段(most_fields)、跨字段相匹配(cross_fields)
默认情况查询的类型是best_fields,例如:

{
  "dis_max": {
    "queries":  [
      {
        "match": {
          "title": {
            "query": "Quick brown fox",
            "minimum_should_match": "30%"
          }
        }
      },
      {
        "match": {
          "body": {
            "query": "Quick brown fox",
            "minimum_should_match": "30%"
          }
        }
      },
    ],
    "tie_breaker": 0.3
  }
}

可以使用multi_match重写成更简洁的形式

{
    "multi_match": {
        "query":                "Quick brown fox",
        "type":                 "best_fields", (1)
        "fields":               [ "title", "body" ],
        "tie_breaker":          0.3,
        "minimum_should_match": "30%" (2)
    }
}

minimum_should_match、operator 这样的参数会被传递到生成的match查询中
匹配的字段可以使用模糊匹配的方式给出,例如:

{
    "multi_match": {
        "query":  "Quick brown fox",
        "fields": "*_title"
    }
}

可以使用^为单个字段提升权重

{
    "multi_match": {
        "query":  "Quick brown fox",
        "fields": [ "*_title", "chapter_title^2" ] (1)
    }
}

多数字段

召回率:返回所有的相关文档
精确率:不返回无关文档
全文搜索的两个名词,目的是在结果的第一页为用户呈现最为相关的文档
多字段映射,首先要做的事情就是对我们的字段索引两次,一次使用词干模式,一次使用非词干模式

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

简单的查询,两个文档的评分相同;只查询title.std那么只有文档2是匹配的

GET /my_index/_search
{
   "query": {
        "match": {
            "title": "jumping rabbits"
        }
    }
}

如果同时查询两个字段,然后使用bool查询将结果合并,那么两个文档都是匹配的,文档2的分更高

GET /my_index/_search
{
   "query": {
        "multi_match": {
            "query":  "jumping rabbits",
            "type":   "most_fields", (1)
            "fields": [ "title", "title.std" ]
        }
    }
}

用广度匹配字段title匹配尽可能多的文档,提升召回率
同时又使用title.std作为信号将相关度更高的文档置于结果顶部

跨字段实体搜索

多字符串查询中,我们为每个字段使用不同的字符串,
而跨字段查询是使用单个字符串在多个字段中进行搜索。
例如我们搜索Poland Street W1V这个地址,它需要匹配多个字段
假如使用motst_fields会出现如下问题:
1、它是为多数字段匹配任意词设计的,而不是在所有字段中找到最匹配的
2、它不能使用operator或minimum_should_match参数来降低相关结果造成的长尾效应
3、词频对于每个字段是不一样的,而且它们之间的相互影响会导致不好的排序结果。

字段中心式查询

most_fields(best_fields)出现的问题都是因为它是字段中心式查询,而不是词中心式的。
词中心式:当真正感兴趣的事匹配词的时候,它为我们查找的是最匹配的字段
问题1:在多个字段中匹配相同的词

GET /_validate/query?explain
{
  "query": {
    "multi_match": {
      "query":   "Poland Street W1V",
      "type":    "most_fields",
      "fields":  [ "street", "city", "country", "postcode" ]
    }
  }
}

生成的explanation解释:

(street:poland   street:street   street:w1v)
(city:poland     city:street     city:w1v)
(country:poland  country:street  country:w1v)
(postcode:poland postcode:street postcode:w1v)

可以发现两个字段都与poland匹配的文档要比一个字段同时匹配poland和street文档的评分高(可能是顺序,得出这样的结论)
问题2:剪掉长尾
在精度匹配中,我们可以使用operator和minimum_should_match参数来消除结果中几乎不相关的长尾,但是对于best_fields和most_fields中,这些参数会在match查询生成时被传入,例如操作符and,就会要求所有词必须存在与相同字段中,这显然是不对的
问题3:词频

{
    "query": {
        "multi_match": {
            "query":       "Peter Smith",
            "type":        "most_fields",
            "fields":      [ "*_name" ]
        }
    }
}

可能在结果中将“Smith Williams” 置于 “Peter Smith” 之上。
简单来说就是smith在名字段中具有较高的IDF,他会削弱“Peter”作为名和“smith”作为姓时低IDF的所起作用。

解决方案

存在这些问题仅仅是因为我们在处理着多个字段,如果将这些字段组合成单个字段,问题就会消失。

{
    "first_name":  "Peter",
    "last_name":   "Smith",
    "full_name":   "Peter Smith"
}

这样只要查询full_name刚才的问题就会消失,但是数据却会冗余。取而代之的是另外两种方案,一个是在索引时,另一个是在搜索时

自定义_all字段

_all字段的索引方式是将所有其他字段的值作为一个大字符串索引的。然而这么做并不灵活,为了灵活,我们可以给人名添加一个自定义_all字段,也为地址添加另一个_all字段
可以使用copy_to参数来实现这个功能

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

first_name和last_name字段的值会被复制到full_name中
copy_to设置对multi_field无效。
多字段只是以不同方式简单索引“主”字段,他们没有自己的数据源,只要对主字段copy_to就能轻而易举的达到相同的效果

PUT /my_index
{
    "mappings": {
        "person": {
            "properties": {
                "first_name": {
                    "type":     "string",
                    "copy_to":  "full_name", (1)
                    "fields": {
                        "raw": {
                            "type": "string",
                            "index": "not_analyzed"
                        }
                    }
                },
                "full_name": {
                    "type":     "string"
                }
            }
        }
    }
}

跨字段查询

搜索时相应的解决方案:使用cross_fields进行multi_match查询。
cross_fields是词中心式的查询,它将所有字段当成一个大字段,并在每个字段中查询每个词

GET /_validate/query?explain
{
    "query": {
        "multi_match": {
            "query":       "peter smith",
            "type":        "most_fields",
            "operator":    "and", (1)
            "fields":      [ "first_name", "last_name" ]
        }
    }
}

得到结果表示peter和smith都必须同时出现在相同字段中,要么是first_name,要么last_name

(+first_name:peter +first_name:smith)
(+last_name:peter  +last_name:smith)

然而词中心式会使用以下逻辑,意味着peter和smith都必须出现,但是可以出现在任意字段中

+(first_name:peter last_name:peter)
+(first_name:smith last_name:smith)

cross_fields类型首先分析查询字符串并生成一个词列表,然后它从所有字段中依次搜索每个词。
这种不同的查询方式自然就解决了前两个问题。
至于IDF问题,它通过混合不同字段逆向索引文档频率的方式解决了词频的问题。

GET /_validate/query?explain
{
    "query": {
        "multi_match": {
            "query":       "peter smith",
            "type":        "cross_fields", (1)
            "operator":    "and",
            "fields":      [ "first_name", "last_name" ]
        }
    }
}
+blended("peter", fields: [first_name, last_name])
+blended("smith", fields: [first_name, last_name])

换句话说,它会在两个字段字段中查找smith的IDF,然后使用最小值作为两个字段的IDF。
Tip:为了让cross_fields查询以最优的方式工作,所有的字段都必须使用相同的分析器,具有相同分析器的字段会被分组在一起作为混合字段使用。
如果包括了不同分析器的字段,它们会以best_fields的相同方式加入到查询结果中。
例如将title字段加入到之前的查询,explanation解释如下:

(+title:peter +title:smith)
(
  +blended("peter", fields: [first_name, last_name])
  +blended("smith", fields: [first_name, last_name])
)

当在使用minimum_should_match和operator参数时,这点尤为重要

按字段提高权重

使用cross_fields和_all相比,其中一个优势就是它可以在搜索时为单个字段提升权重
正如前文提到的^
自定义单字段查询能否优于多字段查询,取决于在多字段与单字段自定义_all之间代价的权衡。

Exact-Value精确值字段

将not_analyzed字段与 multi_match中analyzed字段混在一起没有多大用处。
如前文的查询,假如title是not_analyzed

title:peter smith
(
    blended("peter", fields: [first_name, last_name])
    blended("smith", fields: [first_name, last_name])
)

es会将peter smith完整的字符串作为查询条件来搜索,所以需要在multi_match查询中避免使用not_analyzed字段

你可能感兴趣的:(Multi_field_Search)