以字段为中心的查询(Field-centric Queries)
上述提到的三个问题都来源于most_fields是以字段为中心(Field-centric),而不是以词条为中心(Term-centric):它会查询最多匹配的字段(Most matching fields),而咱们真正感兴趣的最匹配的词条(Most matching terms)。html
NOTE算法
best_fields一样是以字段为中心的,所以它也存在类似的问题。json
首先咱们来看看为何存在这些问题,以及如何解决它们。elasticsearch
问题1:在多个字段中匹配相同的单词
考虑一下most_fields查询是如何执行的:ES会为每一个字段生成一个match查询,让后将它们包含在一个bool查询中。ide
咱们能够将查询传入到validate-query API中进行查看:post
GET /_validate/query?explain
{
"query": { "multi_match": { "query": "Poland Street W1V", "type": "most_fields", "fields": [ "street", "city", "country", "postcode" ] } } }
它会产生下面的解释(explaination):ui
(street:poland street:street street:w1v) (city:poland city:street city:w1v) (country:poland country:street country:w1v) (postcode:poland postcode:street postcode:w1v)spa
你能够发现可以在两个字段中匹配poland的文档会比在一个字段中匹配了poland和street的文档的分值要高。code
问题2:减小长尾
在精度控制(Controlling Precision)一节中,咱们讨论了如何使用and操做符和minimum_should_match参数来减小相关度低的文档数量:htm
{
"query": { "multi_match": { "query": "Poland Street W1V", "type": "most_fields", "operator": "and", "fields": [ "street", "city", "country", "postcode" ] } } }
可是,使用best_fields或者most_fields,这些参数会被传递到生成的match查询中。该查询的解释以下(译注:经过validate-query API):
(+street:poland +street:street +street:w1v) (+city:poland +city:street +city:w1v) (+country:poland +country:street +country:w1v) (+postcode:poland +postcode:street +postcode:w1v)
换言之,使用and操做符时,全部的单词都须要出如今相同的字段中,这显然是错的!这样作可能不会有任何匹配的文档。
问题3:词条频度
在什么是相关度(What is Relevance)一节中,咱们解释了默认用来计算每一个词条的相关度分值的类似度算法TF/IDF:
词条频度(Term Frequency)
在一份文档中,一个词条在一个字段中出现的越频繁,文档的相关度就越高。
倒排文档频度(Inverse Document Frequency)
一个词条在索引的全部文档的字段中出现的越频繁,词条的相关度就越低。
当经过多字段进行搜索时,TF/IDF会产生一些使人惊讶的结果。
考虑使用first_name和last_name字段搜索"Peter Smith"的例子。Peter是一个常见的名字,Smith是一个常见的姓氏 - 它们的IDF都较低。可是若是在索引中有另一个名为Smith Williams的人呢?Smith做为名字是很是罕见的,所以它的IDF值会很高!
像下面这样的一个简单查询会将Smith Williams放在Peter Smith前面(译注:含有Smith Williams的文档分值比含有Peter Smith的文档分值高),尽管Peter Smith明显是更好的匹配:
{
"query": { "multi_match": { "query": "Peter Smith", "type": "most_fields", "fields": [ "*_name" ] } } }
smith在first_name字段中的高IDF值会压倒peter在first_name字段和smith在last_name字段中的两个低IDF值。
解决方案
这个问题仅在咱们处理多字段时存在。若是咱们将全部这些字段合并到一个字段中,该问题就不复存在了。咱们能够向person文档中添加一个full_name字段来实现:
{
"first_name": "Peter", "last_name": "Smith", "full_name": "Peter Smith" }
当咱们只查询full_name字段时:
- 拥有更多匹配单词的文档会赛过那些重复出现一个单词的文档。
- minimum_should_match和operator参数可以正常工做。
- first_name和last_name的倒排文档频度会被合并,所以smith不管是first_name仍是last_name都再也不重要。
尽管这种方法能工做,但是咱们并不想存储冗余数据。所以,ES为咱们提供了两个解决方案 - 一个在索引期间,一个在搜索期间。下一节对它们进行讨论。