在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. GC(垃圾回收器)中的算法

    GC的两种判定方法 (1) 引用计数法 给对象添加一个引用计数器,每当引用一次+1,每次失效时-1,当计数器为0时,表示对象就是不可能再被使用的. (2) 可达性分析算法 将“GC Roots”对象作 ...

  2. JavaScript的三大组成部分

    JavaScript是一种属于网络的脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果.通常JavaScript脚本是通过嵌入在HTML中来实现 ...

  3. center os7 安装mysql

    安装mariadb MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可.开发这个分支的原因之一是:甲骨文公司收购了MySQL后,有将MySQL闭源的潜在风险, ...

  4. vue的列表交错过渡

    参考文章 https://juejin.im/post/5cccf5b0e51d453a907b4af1

  5. MySQL不支持的特性

    MySQL 1.不支持物化视图. 2.不支持位图索引. 3.不支持并行查询. 4.不支持哈希关联,MySQL的所有关联都是嵌套循环关联.不过,可以通过建立一个哈希索引来曲线实现. 5.不允许对同一表同 ...

  6. C#调用谷歌翻译API

    原资料为网上找到的原稿为:http://www.cnblogs.com/marso/p/google_translate_api.html(此处只做个人笔记参考) 主要分两块:通过WebRequest ...

  7. centos 6.5 关闭图形界面

    图形界面的关闭分为临时关闭和永久关闭,临时关闭重启系统后恢复正常,永久关闭重启系统后图形界面仍然为关闭状态. 临时关闭 init 3 永久关闭 vi /etc/inittab 修改下面一行 id:3: ...

  8. kali网络源配置

    使用vim对sources.list文件进行修改: $   vim /etc/apt/sources.list 随便挑选一个源添加到该文件中 ----------------------------- ...

  9. Jmeter下载文件和保存文件

    Jmeter下载文件: 任意在网上搜索一张图片,地址为https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&a ...

  10. hdu 5120 Intersection (圆环面积相交->圆面积相交)

    Problem Description Matt is a big fan of logo design. Recently he falls in love with logo made up by ...