在Elasticsearch全文检索中,我们用的比较多的就是Multi Match Query,其支持对多个字段进行匹配。Elasticsearch支持5种类型的Multi Match,我们一起来深入学习下它们的区别。

5种类型的Multi Match Query

直接从官网的文档上摘抄一段来:

  • best_fields: (default) Finds documents which match any field, but uses the _score from the best field.
  • most_fields: Finds documents which match any field and combines the _score from each field.
  • cross_fields: Treats fields with the same analyzer as though they were one big field. Looks for each word in any field.
  • phrase: Runs a match_phrase query on each field and combines the _score from each field.
  • phrase_prefix: Runs a match_phrase_prefix query on each field and combines the _score from each field.

这里我们只考虑前面三种,后两种可以另外单独研究,就先忽略了。

创建测试索引,预置测试数据

创建gino_product索引

PUT /gino_product
{
"mappings": {
"product": {
"properties": {
"productName": {
"type": "string",
"analyzer": "fulltext_analyzer",
"copy_to": [
"bigSearchField"
]
},
"brandName": {
"type": "string",
"analyzer": "fulltext_analyzer",
"copy_to": [
"bigSearchField"
],
"fields": {
"brandName_pinyin": {
"type": "string",
"analyzer": "pinyin_analyzer",
"search_analyzer": "standard"
},
"brandName_keyword": {
"type": "string",
"analyzer": "keyword",
"search_analyzer": "standard"
}
}
},
"sortName": {
"type": "string",
"analyzer": "fulltext_analyzer",
"copy_to": [
"bigSearchField"
],
"fields": {
"sortName_pinyin": {
"type": "string",
"analyzer": "pinyin_analyzer",
"search_analyzer": "standard"
}
}
},
"productKeyword": {
"type": "string",
"analyzer": "fulltext_analyzer",
"copy_to": [
"bigSearchField"
]
},
"bigSearchField": {
"type": "string",
"analyzer": "fulltext_analyzer"
}
}
}
},
"settings": {
"index": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"analysis": {
"tokenizer": {
"simple_pinyin": {
"type": "pinyin",
"first_letter": "none"
}
},
"analyzer": {
"fulltext_analyzer": {
"type": "ik",
"use_smart": true
},
"pinyin_analyzer": {
"type": "custom",
"tokenizer": "simple_pinyin",
"filter": [
"word_delimiter",
"lowercase"
]
}
}
}
}
}

插入一些测试数据

POST /gino_product/product/1
{
"productName": "耐克女生运动轻跑鞋",
"brandName": "耐克",
"sortName": "鞋子",
"productKeyword": "耐克,潮流,运动,轻跑鞋"
} POST /gino_product/product/2
{
"productName": "耐克女生休闲运动服",
"brandName": "耐克",
"sortName": "上衣",
"productKeyword": "耐克,休闲,运动"
} POST /gino_product/product/3
{
"productName": "阿迪达斯女生冬季运动板鞋",
"brandName": "阿迪达斯",
"sortName": "鞋子",
"productKeyword": "阿迪达斯,冬季,运动,板鞋"
} POST /gino_product/product/4
{
"productName": "阿迪达斯女生冬季运动夹克外套",
"brandName": "阿迪达斯",
"sortName": "上衣",
"productKeyword": "阿迪达斯,冬季,运动,夹克,外套"
}

测试数据总览

分别搜索【运动】

POST /gino_product/_search
{
"query": {
"multi_match": {
"query": "运动",
"fields": [
"brandName^100",
"brandName.brandName_pinyin^100",
"brandName.brandName_keyword^100",
"sortName^80",
"sortName.sortName_pinyin^80",
"productName^60",
"productKeyword^20"
],
"type": <multi-match-type>,
"operator": "AND"
}
}
}

发现使用3种type都可以搜索出4条商品数据,而且排序也是一致的。

分别搜索【运动 上衣】

POST /gino_product/_search
{
"query": {
"multi_match": {
"query": "运动 上衣",
"fields": [
"brandName^100",
"brandName.brandName_pinyin^100",
"brandName.brandName_keyword^100",
"sortName^80",
"sortName.sortName_pinyin^80",
"productName^60",
"productKeyword^20"
],
"type": <multi-match-type>,
"operator": "AND"
}
}
}

这次搜索只有cross_field才能搜索出数据,而使用best_fields和most_fields不行,为什么?

使用validate API来比较区别

POST /gino_product/_validate/query?rewrite=true
{
"query": {
"multi_match": {
"query": "运动 上衣",
"fields": [
"brandName^100",
"brandName.brandName_pinyin^100",
"brandName.brandName_keyword^100",
"sortName^80",
"sortName.sortName_pinyin^80",
"productName^60",
"productKeyword^20"
],
"type": <multi-match-type>,
"operator": "AND"
}
}
}

best_fields:所有输入的Token必须在一个字段上全部匹配。

每个字段匹配时分别使用mapping上定义的analyzersearch_analyzer

(+brandName:运动 +brandName:上衣)^100.0
| (+brandName.brandName_pinyin:运 +brandName.brandName_pinyin:动 +brandName.brandName_pinyin:上 +brandName.brandName_pinyin:衣)^100.0
| (+brandName.brandName_keyword:运 +brandName.brandName_keyword:动 +brandName.brandName_keyword:上 +brandName.brandName_keyword:衣)^100.0
| (+sortName:运动 +sortName:上衣)^80.0
| (+sortName.sortName_pinyin:运 +sortName.sortName_pinyin:动 +sortName.sortName_pinyin:上 +sortName.sortName_pinyin:衣)^80.0
| (+productName:运动 +productName:上衣)^60.0
| (+productKeyword:运动 +productKeyword:上衣)^20.0

most_fields:所有输入的Token必须在一个字段上全部匹配。

best_fields不同之处在于相关性评分,best_fields取最大匹配得分(max计算),而most_fields取所有匹配之和(sum计算)。

(
(+brandName:运动 +brandName:上衣)^100.0
(+brandName.brandName_pinyin:运 +brandName.brandName_pinyin:动 +brandName.brandName_pinyin:上 +brandName.brandName_pinyin:衣)^100.0
(+brandName.brandName_keyword:运 +brandName.brandName_keyword:动 +brandName.brandName_keyword:上 +brandName.brandName_keyword:衣)^100.0
(+sortName:运动 +sortName:上衣)^80.0
(+sortName.sortName_pinyin:运 +sortName.sortName_pinyin:动 +sortName.sortName_pinyin:上 +sortName.sortName_pinyin:衣)^80.0
(+productName:运动 +productName:上衣)^60.0
(+productKeyword:运动 +productKeyword:上衣)^20.0
)

cross_fields:所有输入的Token必须在同一组的字段上全部匹配。

首先ES会对cross_fields进行查询重写分组,分组的依据是search_analyzer。具体到我们的例子中【brandName.brandName_pinyin、brandName.brandName_keyword、sortName.sortName_pinyin】这三个字段的search_analyzer是standard,而其余的字段是fulltext_analyzer,因此最终被分为了两组。

(
(
+(brandName.brandName_pinyin:运^100.0 | sortName.sortName_pinyin:运^80.0 | brandName.brandName_keyword:运^100.0)
+(brandName.brandName_pinyin:动^100.0 | sortName.sortName_pinyin:动^80.0 | brandName.brandName_keyword:动^100.0)
+(brandName.brandName_pinyin:上^100.0 | sortName.sortName_pinyin:上^80.0 | brandName.brandName_keyword:上^100.0)
+(brandName.brandName_pinyin:衣^100.0 | sortName.sortName_pinyin:衣^80.0 | brandName.brandName_keyword:衣^100.0)
)
(
+(productKeyword:运动^20.0 | brandName:运动^100.0 | sortName:运动^80.0 | productName:运动^60.0)
+(productKeyword:上衣^20.0 | brandName:上衣^100.0 | sortName:上衣^80.0 | productName:上衣^60.0)
)
)

继续探索和思考

如何让best_fields和most_fields也可以匹配出商品?

最常见的做法就是使用_all字段或者copyTo字段来实现,比如我们mapping里面的bigSearchField字段。

如何改进cross_fields的搜索结果?

由于cross_fields需要根据search_analyzer进行分组,因此像搜索【运动 shangyi】这样的输入时是无法匹配到商品的,因此应该尽可能地减少分组既尽量使用统一的search_analyzer,或者在search时强制指定search_analyzer覆盖mapping里定义的search_analyzer。

把operator改成OR会如何?

在上面的例子中,我们设置的operator均为AND,意味着所有搜索的Token都必须被匹配。那设置成OR会怎么样以及什么场景下该使用OR呢?

在使用OR的时候要特别注意,因为只要有一个Token匹配就会把商品搜索出来,比如上面的搜索【运动 上衣】的时候,会把鞋子的商品也匹配出来,这样搜索的准确度会远远降低。

在一些特殊的搜索中,比如我们搜索【耐克 阿迪达斯 上衣】,如果使用operator为AND,则无论使用哪种multi-search-type都无法匹配出商品(想想为什么?),此时我们可以设置operator为OR并且设置minimum_should_match为60%,这样就可以搜索出属于耐克和阿迪达斯的上衣了,这种情况相当于一种智能的搜索降级了。

/gino_product/_search
{
"query": {
"multi_match": {
"query": "耐克 阿迪达斯 上衣",
"fields": [
"brandName^100",
"brandName.brandName_pinyin^100",
"brandName.brandName_keyword^100",
"sortName^80",
"sortName.sortName_pinyin^80",
"productName^60",
"productKeyword^20"
],
"type": "cross_fields",
"operator": "OR",
"minimum_should_match": "60%"
}
}
}

再谈相关性评分

Elasticsearch相关性打分机制学习一文中我们曾经探讨过best_fields和cross_fields相关性评分的机制,其中的例子使用的相同的search_analyzer。那对于分组情况下,cross_fields评分又是如何计算的呢?

我们还是用上面的例子,增加explain参数来看一下。

POST /gino_product/_search
{
"explain": true,
"query": {
"multi_match": {
"query": "运动 上衣",
"fields": [
"brandName^100",
"brandName.brandName_pinyin^100",
"brandName.brandName_keyword^100",
"sortName^80",
"sortName.sortName_pinyin^80",
"productName^60",
"productKeyword^20"
],
"type": "cross_fields",
"operator": "AND"
}
}
}

详细ES响应报文:cross_fields_scoring.json

通过上述validate API得到的分组信息和explain得到的评分详情信息,可以总结出一个cross_fields评分公式:

score(q, d) = coord(q, d) * ∑(∑(max(score(t, f))))
  • coord(q, d): 分组匹配因子,比如上面我们只有一个分组匹配,coord就是0.5(两个分组中匹配了一个分组);
  • score(t, f): 搜索的一个Token和一个特定的字段的相关性评分(使用TFIDF)计算;
  • max:搜索的一个Token在所有字段评分中取最大值;
  • 分组内求和:一个分组内搜索的所有Token的最大值进行求和;
  • 分组间求和:所有分组的得分最终进行求和计算;

小结

  • best_fields对搜索为单个Token的情况下效果更好,比如搜索【耐克】的时候品牌为耐克和商品关键字包含耐克的时候前者相关性得分更高;但是对于都是为多个Token需要跨字段匹配时,只能引进大字段来匹配,这样权重的设置就失去意义了;
  • most_fields和best_fields类似,其优点在于能够尽可能多地匹配,相关性评分机制更合理;
  • cross_fields最大的优点在于能够跨字段匹配,而且充分利用到了各个字段的权重设置。但是需要注意的是匹配时是根据search_analyzer进行分组,不同分组直接的匹配无法跨字段。

参考材料

elasticsearch 中的Multi Match Query的更多相关文章

  1. Elasticsearch Query DSL 整理总结(四)—— Multi Match Query

    目录 引言 概要 fields 字段 通配符 提升字段权重 multi_match查询的类型 best_fields 类型 dis_max 分离最大化查询 best_fields 维权使者 tie_b ...

  2. elasticsearch 嵌套对象使用Multi Match Query、query_string全文检索设置

    参考: https://www.elastic.co/guide/en/elasticsearch/reference/1.7/mapping-nested-type.html https://sta ...

  3. ElasticSearch中term和match探索

    一.创建测试数据 1.创建一个index curl -X PUT http://127.0.0.1:9200/student?pretty -H "Content-Type: applica ...

  4. Elasticsearch Query DSL 整理总结(二)—— 要搞懂 Match Query,看这篇就够了

    目录 引言 构建示例 match operator 参数 analyzer lenient 参数 Fuzziness fuzzniess 参数 什么是模糊搜索? Levenshtein Edit Di ...

  5. Elasticsearch 5.x 关于term query和match query的认识

    http://blog.csdn.net/yangwenbo214/article/details/54142786 一.基本情况 前言:term query和match query牵扯的东西比较多, ...

  6. ES 20 - 查询Elasticsearch中的数据 (基于DSL查询, 包括查询校验match + bool + term)

    目录 1 什么是DSL 2 DSL校验 - 定位不合法的查询语句 3 match query的使用 3.1 简单功能示例 3.1.1 查询所有文档 3.1.2 查询满足一定条件的文档 3.1.3 分页 ...

  7. Elasticsearch.Net 异常:[match] query doesn't support multiple fields, found [field] and [query]

    用Elasticsearch.Net检索数据,报异常: )); ElasticLowLevelClient client = new ElasticLowLevelClient(settings); ...

  8. elasticsearch中常用的API

    elasticsearch中常用的API分类如下: 文档API: 提供对文档的增删改查操作 搜索API: 提供对文档进行某个字段的查询 索引API: 提供对索引进行操作,查看索引信息等 查看API: ...

  9. 如何在Elasticsearch中安装中文分词器(IK+pinyin)

    如果直接使用Elasticsearch的朋友在处理中文内容的搜索时,肯定会遇到很尴尬的问题--中文词语被分成了一个一个的汉字,当用Kibana作图的时候,按照term来分组,结果一个汉字被分成了一组. ...

随机推荐

  1. shortcut to add throws declaration in Intellij Idea

    When a piece of code needs error handling, IntelliJ underlines it with red. Set your pointer on that ...

  2. 【串线篇】SQL映射文件select简单查询标签

    一.参数(Parameters)传递 单个参数 基本类型:取值#{hahaha}随便写 多个参数 <!--   public Employee getEmpById(Integer id,Str ...

  3. H2database创建表

    语法和sql server大同小异 create table users(id int primary key not null int identity, name varchar(20))

  4. loj6177 「美团 CodeM 初赛 Round B」送外卖2 最短路+状压dp

    题目传送门 https://loj.ac/problem/6177 题解 一直不知道允不允许这样的情况:取了第一的任务的货物后前往配送的时候,顺路取了第二个货物. 然后发现如果不可以这样的话,那么原题 ...

  5. 谷歌开发人员在现代Web浏览器中发现严重跨域漏洞

    Google谷歌研究人员在现代网络浏览器中发现了一个严重漏洞,该漏洞可能允许您访问的网站从您登录同一浏览器的其他网站窃取您的在线帐户的敏感内容. 由Google谷歌Chrome的开发者支持者Jake发 ...

  6. 02.自定义banner、全局配置文件、@Value获取自定义配置、@ConfigurationProperties、profiles配置

    自定义banner src/main/resource 下新建 banner.txt,字符复制到banner.txt 中 生成字符网站推荐: http://patorjk.com/software/t ...

  7. 理解"__repr__"

    class aTest: def __repr__(self): return "This is an aTest class." a = aTest() print (a) cl ...

  8. Webpack 4 和单页应用入门

    引言 本文转自https://github.com/wallstreetcn/webpack-and-spa-guide,为了方便阅读转到博客园. webpack 更新到了 4.0,官网还没有更新文档 ...

  9. 51nod 1836:战忽局的手段(期望)

    题目链接 公式比较好推 精度好难搞啊@_@ 下面记笔记@_@ ****在CodeBlocks中,输出double型变量要使用%f (参见http://bbs.csdn.net/topics/39193 ...

  10. VC++ 控件赋值取值

    SetWindowText(SetWindowTextW)void SetWindowText(  LPCTSTR lpszString  );GetWindowText(GetWindowTextW ...