本篇介绍一下multi_match的best_fields、most_fields和cross_fields三种语法的场景和简单示例。java
bool查询采起"more-matches-is-better"匹配越多分越高的方式,因此每条match语句的评分结果会被加在一块儿,从而为每一个文档提供最终的分数_score。能与两条语句同时匹配的文档会比只与一条语句匹配的文档得分要高,但有时这样也会带来一些与指望不符合的状况,咱们举个例子:算法
咱们以英文儿歌为案例背景,咱们这样搜索:微信
GET /music/children/_search { "query": { "bool": { "should": [ { "match": { "name": "brush mouth" }}, { "match": { "content": "you sunshine" }} ] } } }
结果响应(有删减)架构
{ "hits": { "total": 2, "max_score": 1.7672573, "hits": [ { "_id": "4", "_score": 1.7672573, "_source": { "name": "brush your teeth", "content": "When you wake up in the morning it's a quarter to one, and you want to have a little fun You brush your teeth" } }, { "_id": "3", "_score": 0.7911257, "_source": { "name": "you are my sunshine", "content": "you are my sunshine, my only sunshine, you make me happy, when skies are gray" } } ] } }
预期的结果是"you are my sunshine"要排在"brush you teeth"前面,实际结果却相反,为何呢?并发
咱们按照匹配的方式复原一下_score的评分过程:每一个query的分数,乘以匹配的query的数量,除以总query的数量。app
咱们来看一下匹配状况:
文档4的name字段包含brush,content字段包含you,因此两个match都能获得评分。
文档3的name字段不匹配,可是content字段包含you和sunshine,命中一个match,只能得一项的分。
结果文档4的得分会高一些。分布式
但咱们仔细想想,文档4虽然两个match都匹配了,但每一个match只匹配了其中一个关键词,文档3只匹配了一个match,倒是同时匹配了两个连续的关键词,按咱们的预期,一个field上匹配了两个连续关键词的相关性应该高一些,简单的把多个match的得分加起来,虽然分高一些,但不是咱们指望的首位。高并发
咱们探寻的是最佳字段匹配,某一个字段匹配到了尽量多的关键词,让它排在前面;而不是更多的field匹配了关键词,就让它在前面。优化
咱们使用dis_max语法查询,优先将最佳匹配的评分做为查询的评分结果返回,请求以下:搜索引擎
GET /music/children/_search { "query": { "dis_max": { "queries": [ { "match": { "name": "brush mouth" }}, { "match": { "content": "you sunshine" }} ] } } }
结果响应(有删减)
{ "hits": { "total": 2, "max_score": 1.0310873, "hits": [ { "_id": "4", "_score": 1.0310873, "_source": { "name": "brush your teeth", "content": "When you wake up in the morning it's a quarter to one, and you want to have a little fun You brush your teeth" } }, { "_id": "3", "_score": 0.7911257, "_source": { "name": "you are my sunshine", "content": "you are my sunshine, my only sunshine, you make me happy, when skies are gray" } } ] } }
呃,结果排序仍是不理想,不过能够看到_id为4的评分由以前的1.7672573降为1.0310873,说明dis_max操做后,可以影响评分,只是案例取得很差,_id为3的记录评分实在过低了,只有0.7911257,仍然不能改变次序。
上一节的dis_max查询会采用单个最佳匹配字段,而忽略其余的匹配项,这对精准化搜索仍是不够合理,咱们须要其余匹配项的匹配结果按必定权重参与最后的评分,权重能够本身设置。
咱们能够加一个tie_breaker参数,这样就能够把其余匹配项的结果也考虑进去,它的使用规则以下:
因此说,加上了tie_breaker,会考虑全部的匹配条件,但最佳匹配语句仍然占大头。
请求示例:
GET /music/children/_search { "query": { "dis_max": { "queries": [ { "match": { "name": "brush mouth" }}, { "match": { "content": "you sunshine" }} ], "tie_breaker": 0.3 } } }
best-fields策略:将某一个field匹配了尽量多关键词的文档优先返回回来。
若是咱们在多个字段上使用相同的搜索字符串进行搜索,请求语法能够冗长一些:
GET /music/children/_search { "query": { "dis_max": { "queries": [ { "match": { "name": { "query": "you sunshine", "boost": 2, "minimum_should_match": "50%" } } }, { "match": { "content": "you sunshine" } } ], "tie_breaker": 0.3 } } }
能够用multi_match将搜索请求简化,multi_match支持boost、minimum_should_match、tie_breaker参数的设置:
GET /music/children/_search { "query": { "multi_match": { "query": "you sunshine", "type": "best_fields", "fields": ["name^2","content"], "minimum_should_match": "50%", "tie_breaker": 0.3 } } }
而boost、minimum_should_match、tie_breaker参数的一个显著做用就是去长尾,长尾数据好比说咱们搜索4个关键词,但不少文档只匹配1个,也显示出来了,这些文档其实不是咱们想要的,能够经过这几个参数的设置,将门槛提升,过滤掉长尾数据。
most-fields策略:尽量返回更多field匹配到某个关键词的doc,优先返回回来。
经常使用方式是咱们为同一文本字段,创建多种方式的索引,词干提取分析处理的和原文存储的都作一份,这样能提升匹配的精准度。
咱们拿music索引举个例子(摘抄mapping片段信息)。咱们作一点小修改:
PUT /music { "mappings": { "children": { "properties": { "name": { "type": "text", "analyzer": "english" "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "content": { "type": "text", "analyzer": "english" "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } } }
好比name和content字段,咱们除了有text类型的字段,还有keyword类型的子字段,text会作分词、英文词干处理,keywork则保持原样,搜索内容的时候,咱们可使用name或name.keyword两个字段同时进行搜索,示例:
GET /music/children/_search { "query": { "multi_match": { "query": "brushed", "type": "most_fields", "fields": ["name","name.keyword"] } } }
咱们搜索name及name.keyword两个字段,因为name字段的分词器是english,搜索字符串brushed通过提取词干后变成brush,是能匹配到结果的,name.keyword则没法匹配,最终仍是有文档结果返回。若是只对name.keyword字段搜索,则不会有结果返回。
这个就是most_fields的策略,但愿对同一个文本进行多种索引,搜索时各类索引的结果都参与,这样就能尽量地多返回结果。
实际的例子:百度之类的搜索引擎,最匹配的到最前面,可是其余的就没什么区分度了
实际的例子:wiki,明显的most_fields策略,搜索结果比较均匀,可是的确要翻好几页才能找到最匹配的结果
有些实体对象在设计中,可能会使用多个字段来标识一个信息,如地址,常见存储方案能够是省、市、区、街道四个字段,分别存储,合起来才是完整的地址信息。再如人名,国外有first name和last name之分。
遇到针对这种字段的搜索,咱们叫作跨字段实体搜索,咱们要注意哪些问题呢?
咱们回顾music索引的author字段,就是设计成了author_first_name和author_last_name的结构,咱们试着对它来演示一下跨字段实体搜索。
GET /music/children/_search { "query": { "multi_match": { "query": "Peter Raffi", "type": "most_fields", "fields": [ "author_first_name", "author_last_name" ] } } }
响应的结果:
{ "hits": { "total": 2, "max_score": 1.3862944, "hits": [ { "_id": "4", "_score": 1.3862944, "_source": { "id": "55fa74f7-35f3-4313-a678-18c19c918a78", "author_first_name": "Peter", "author_last_name": "Raffi", "author": "Peter Raffi", "name": "brush your teeth", "content": "When you wake up in the morning it's a quarter to one, and you want to have a little fun You brush your teeth" } }, { "_id": "1", "_score": 0.2876821, "_source": { "author_first_name": "Peter", "author_last_name": "Gymbo", "author": "Peter Gymbo", "name": "gymbo", "content": "I hava a friend who loves smile, gymbo is his name" } } ] } }
看起来结果是对的,"Peter Raffi"按预期排在首位,但Peter Gymbo也出来的,这不是咱们想要的结果,只是因为数据量太少的缘由,长尾数据没有显示出来,most_fields查询引出的问题有以下3个:
copy_to语法能够将多个字段合并在一块儿,这样就能够解决跨实体字段的问题,带来的副面影响就是占用更多的存储空间,copy_to的示例以下:
PUT /music/_mapping/children { "properties": { "author_first_name": { "type": "text", "copy_to": "author_full_name" }, "author_last_name": { "type": "text", "copy_to": "author_full_name" }, "author_full_name": { "type": "text" } } }
注意这个请求须要在创建索引时执行,局限性比较大。
因此案例设计时,专门有一个author字段,存储完整的名称的。
GET /music/children/_search { "query": { "match": { "author_full_name": { "query": "Peter Raffi", "operator": "and" } } } }
单字段的查询,就能够为所欲为的指定operator或minimum_should_match来控制精度了。
咱们看一下前面提到的3个问题可否解决
解决,最匹配的数据优先返回。
解决,能够指定operator或minimum_should_match来控制精度。
解决,全部信息在一个字段里,IDF计算时次数是均匀的,不会有极端的偏差。
缺点:
须要前期设计时冗余字段,占用的存储会多一些。
copy_to拼接字段时,会遇到顺序问题,如英文名称名前姓后,而地址顺序则不固定,有的从省到街道由大到小,有的是反的,这也是局限性之一。
multi_match有原生的cross_fields语法解决跨字段实体搜索问题,请求以下:
GET /music/children/_search { "query": { "multi_match": { "query": "Peter Raffi", "type": "cross_fields", "operator": "and", "fields": ["author_first_name", "author_last_name"] } } }
此次cross_fields的含义是要求:
看看上面说起的3个问题解决状况:
解决,cross_fields要求每一个term都必须在任何一个field中出现
解决,参见上一条,每一个term都必须匹配,长尾问题天然迎刃而解。
解决,cross_fields经过混合不一样字段逆向索引文档频率的方式解决词频的问题,具体来讲,Peter在first_name中频率会高一些,在last_name中频率会低一些,在两个字段获得的IDF值,会取小的那个,Raffi也是一样处理,这样获得的IDF值就比较正常,不会偏高。
咱们能够花一点时间了解一下多字段搜索的场景,和要注意的细节点,精准搜索是一个很是大的话题,优化的空间没有上限,能够先从最基础的场景和调整语法开始尝试。
专一Java高并发、分布式架构,更多技术干货分享与心得,请关注公众号:Java架构社区
能够扫左边二维码添加好友,邀请你加入Java架构社区微信群共同探讨技术