1、multi_match是啥?
概念:
多字段检索,是组合查询的另一种形态,考试的时候如果考察多字段检索,并不一定必须使用multi_match,使用bool query,只要结果正确亦可,除非题目中明确要求(目前没有强制要求过)
语法:
GET <index>/_search { "query": { "multi_match": { "query": "<query keyword>", "type": "<multi_match_type>", "fields": [ "<field_a>", "<field_b>" ] } } }
2、multi_match和_source区别
- multi_match:从哪些字段中检索,指的是查询条件
- _source:查询的结果包含哪些字段,指的是元数据
3、multi_match type:
3.1 best_fields:
3.1.1 概念:
侧重于字段
维度,单个字段的得分权重大,对于同一个query,单个field匹配更多的term,则优先排序。
3.1.2 用法:
注意,best_fields是multi_match中type的默认值
GET product/_search { "query": { "multi_match" : { "query": "super charge", "type": "best_fields", // 默认 "fields": [ "name^2", "desc" ], "tie_breaker": 0.3 } } }
3.1.3 案例
针对于以下查询,包含两个查询条件:分别是条件1和条件2
GET product/_search { "query": { "dis_max": { "queries": [ { "match": { "name": "chiji shouji" }}, #条件1 { "match": { "desc": "chiji shouji" }} #条件2 ] } } }
假设上述查询的执行得到以下结果,best_fields策略强调hits中单个字段的评分权重。打个比方:每一条hit代表一个奥运会的参加国,每个字段代表该国家的参赛运动员,但是限定每个国家只能派出一名运动员,其成绩就代表该国家的成绩,最后以该运动员的成绩代表国家进行排名。所谓best_fields就是说最好的字段嘛,用最好的字段的评分代表当前文档的最终评分,即侧重字段权重。在这个例子中,多个查询条件并未起到关键性作用。
3.1.4 tie_breaker参数
在best_fields策略中给其他剩余字段设置的权重值,取值范围 [0,1],其中 0 代表使用 dis_max 最佳匹配语句的普通逻辑,1表示所有匹配语句同等重要。最佳的精确值需要根据数据与查询调试得出,但是合理值应该与零接近(处于 0.1 - 0.4 之间),这样就不会颠覆 dis_max (Disjunction Max Query)最佳匹配性质的根本。
在上面例子中,如果一个国家仅仅由一个运动员的成绩来决定,显然不是很有代表性,因为一个国家可能整体实力很弱,但是有一个运动员(假设叫做阿尔法)就是特别的出类拔萃,世界第一!但是其他人都很弱,这时他就不能代表整个国家的实力了。反而可能另一个国家,虽然国内成绩最好的运动员没有阿尔法的成绩好,但是这个国家包揽了世界的第二名到第十名,并且实力比较接近,那这样这个国家的整体实力仍然是可以排第一的。所以我们不应该让第一名完全代表一个国家的成绩,一个更好的做法是:每个国家的最终成绩由所有运动员的成绩经过计算得来,每个运动员的成绩都可能影响总成绩,但是这个国家排名第一的运动员的成绩占的权重最大。这种做法更容易凸显一个国家的整体实力,这个整体实力就等价于我们搜索结果排名中的相关度。
用法:
GET product/_search { "query": { "dis_max": { "queries": [ { "match": { "name": "super charge" }}, { "match": { "desc": "super charge" }} ], "tie_breaker": 0.3 # 代表次要评分字段的权重是 0.3 } } }
3.1.5 类比
以下两个查询等价
查询1
GET product/_search { "query": { "dis_max": { "queries": [ { "match": { "name": { "query": "chiji shouji", "boost": 2 # name字段评分两倍权重 } } }, { "match": { "desc": "chiji shouji" } } ], "tie_breaker": 0.3 } } }
查询2
GET product/_search { "query": { "multi_match" : { "query": "super charge", "type": "best_fields", // 默认 "fields": [ "name^2", "desc" ], # name字段评分两倍权重 "tie_breaker": 0.3 } } }
3.2 most_fields:
3.2.1 概念
侧重于查询
维度,单个查询条件的得分权重大,如果一次请求中,对于同一个doc,匹配到某个term的field越多,则越优先排序。
3.2.2 类比
以下两个查询脚本等价
查询1:
# 下面查询中包含两个查询条件 GET product/_search { "query": { "bool": { "should": [ { "match": { "name": "chiji shouji" } }, { "match": { "desc": "chiji shouji" } } ] } } }
查询2
GET product/_search { "query": { "multi_match": { "query": "chiji shouji", "type": "most_fields", "fields": [ "name", "desc" ] } } }
3.3 cross_fields:
注意:理解cross_fields
的概念之前,需要对ES的评分规则有基本的了解,戳:评分,学习ES评分的基本原理
3.3.1 概念
将任何与任一查询匹配的文档作为结果返回,但只将最佳匹配的评分作为查询的评分结果返回
3.3.2 用法
# 以下查询语义: # 吴 必须包含在 name.姓 或者 name.名 里 # 或者 # 磊 必须包含在 name.姓 或者 name.名 里 GET teacher/_search { "query": { "multi_match" : { "query": "吴磊", "type": "cross_fields", "fields": [ "name.姓", "name.名" ], "operator": "or" } } }
3.3.3 案例
假设我们有如下teacher
索引,索引中包含了name
字段,包含姓
和名
两个field(案例中使用中文为方便观察和理解,切勿在生产环境中使用中文命名,一定要遵循命名规范)。
POST /teacher/_bulk { "index": { "_id": "1"} } { "name" : {"姓" : "吴", "名" : "磊"} } { "index": { "_id": "2"} } { "name" : {"姓" : "连", "名" : "鹏鹏"} } { "index": { "_id": "3"} } { "name" : { "姓" : "张","名" : "明明"} } { "index": { "_id": "4"} } { "name" : { "姓" : "周","名" : "志志"} } { "index": { "_id": "5"} } { "name" : {"姓" : "吴", "名" : "亦凡"} } { "index": { "_id": "6"} } { "name" : {"姓" : "吴", "名" : "京"} } { "index": { "_id": "7"} } { "name" : {"姓" : "吴", "名" : "彦祖"} } { "index": { "_id": "8"} } { "name" : {"姓" : "帅", "名" : "吴"} } { "index": { "_id": "9"} } { "name" : {"姓" : "连", "名" : "磊"} } { "index": { "_id": "10"} } { "name" : {"姓" : "周", "名" : "磊"} } { "index": { "_id": "11"} } { "name" : {"姓" : "张", "名" : "磊"} } { "index": { "_id": "12"} } { "name" : {"姓" : "马", "名" : "磊"} } #{ "index": { "_id": "13"} } #{ "name" : {"姓" : "诸葛", "名" : "吴磊"} }
我们执行以上代码创建teacher索引,并且执行以下查询语句
# 语义: 默认分词器的对`吴磊`的分词结果为`吴`和`磊` # name.姓 中包含 `吴` 或者 `磊` # OR # name.名 中包含 `吴` 或者 `磊` # 如果设置了"operator": "and",则中间 OR 的关系变为 AND GET teacher/_search { "query": { "multi_match": { "query": "吴 磊", "type": "most_fields", "fields": [ "name.姓", "name.名" ] // ,"operator": "and" } } }
根据上面查询的语义,我们期望的结果是:姓为吴
并且名为磊
的doc评分最高,然而结果却如下:
{ "took" : 3, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 9, "relation" : "eq" }, "max_score" : 2.4548545, "hits" : [ { "_index" : "teacher", "_type" : "_doc", "_id" : "8", "_score" : 2.4548545, "_source" : { "name" : { "姓" : "帅", "名" : "吴" } } }, { "_index" : "teacher", "_type" : "_doc", "_id" : "1", "_score" : 2.03873, "_source" : { "name" : { "姓" : "吴", "名" : "磊" } } }, { "_index" : "teacher", "_type" : "_doc", "_id" : "5", "_score" : 1.060872, "_source" : { "name" : { "姓" : "吴", "名" : "亦凡" } } }, { "_index" : "teacher", "_type" : "_doc", "_id" : "6", "_score" : 1.060872, "_source" : { "name" : { "姓" : "吴", "名" : "京" } } }, { "_index" : "teacher", "_type" : "_doc", "_id" : "7", "_score" : 1.060872, "_source" : { "name" : { "姓" : "吴", "名" : "彦祖" } } }, { "_index" : "teacher", "_type" : "_doc", "_id" : "9", "_score" : 0.977858, "_source" : { "name" : { "姓" : "连", "名" : "磊" } } }, { "_index" : "teacher", "_type" : "_doc", "_id" : "10", "_score" : 0.977858, "_source" : { "name" : { "姓" : "周", "名" : "磊" } } }, { "_index" : "teacher", "_type" : "_doc", "_id" : "11", "_score" : 0.977858, "_source" : { "name" : { "姓" : "张", "名" : "磊" } } }, { "_index" : "teacher", "_type" : "_doc", "_id" : "12", "_score" : 0.977858, "_source" : { "name" : { "姓" : "马", "名" : "磊" } } } ] } }
上面结果显示,名叫帅磊的doc排在了最前面,即便我们使用best_fields策略结果也是帅磊评分最高,因为导致这个结果的原因和使用哪种搜索策略并无关系。这些然不是我们希望的结果。那么导致上述问题的原因是什么呢?看以下三条基本的评分规则:
评分基本规则:
- 词频(TF term frequency ):关键词在每个doc中出现的次数,词频越高,评分越高
- 反词频( IDF inverse doc
frequency):关键词在整个索引中出现的次数,反词频越高,评分越低 - 每个doc的长度,越长相关度评分越低
分析结果:
在上述案例中,吴磊
作为预期结果,其中吴
字作为姓氏是非常常见的,磊
作为名也是非常常见的。反应在索引中,他们的IDF都是非常高的,而反词频越高则评分越低,因此吴磊在索引中的IDF评分则会很低。
而帅磊
中帅
作为姓氏却是非常少见的,因此IDF的得分就很高。在词频相同的情况下就会导致以上不符合常理的搜索预期。
解决办法:
为了避免某一个字段的词频或者反词频对结果产生巨大影响,我们需要把姓
和名
作为一个整体来查询,体现在代码上即:
# 吴 必须包含在 name.姓 或者 name.名 里 # 并且 # 磊 必须包含在 name.姓 或者 name.名 里 GET teacher/_search { "query": { "multi_match" : { "query": "吴磊", "type": "cross_fields", "fields": [ "name.姓", "name.名" ], "operator": "and" } } }