elasticsearch---search in depth之multi-field search

1:multiple query string

我们通常感兴趣的查询不仅仅是限定在一个字段,而是多个字段。因此bool-query + match-query是一种常见的组合方法,来达到多字段查询的目的。

bool-query是一种more-match-is-better的query方式,也就是match的越多,score越高。可见score的计算是叠加match子查询的score。

然后,bool-query的子查询同样可以是bool-query。注意score的计算。

比如,情况一:四个match-query组合在一起,位于同一个level。情况二:两个match-query首先组成一个bool-query,然后跟其余两个match-query组合,成为一个bool-query。

显然level不一样了。这种情况下,score的计算也有所不同,位于同一level的query具有相同的权重。因此情况一各个权重都是1/4,情况而二中是1/3 1/3 3/1(两个match)。

当然如果某子查询需要具有更高的权重,可以通过制定boost值来实现。

2:single-query-string

bool-query是multiple-query的基础,大多情况下都非常适用。但是,人们希望将他们认知的查询全部统一到一个field中去。面对如此需求,应该如何处理。多余multi-field,multi-word的query,首先需要充分了解data,然后选择合适的策略。以下三种为es提供的查询策略

(1)most-field:对于查询terms,越多的field能match,score越高

(2)best-field:对于查询terms,同一个field的契合度最好的,score最高

(3)crossfield:对于查询terms,fields中总体match的越多,score越高。

下边详细解释以上的应用场景。

3:best-fields

doc1: 

“title”: "Quick brown rabbits"

"body": "Brown rabbits are commonly seen."

doc2:

"title" : "Keeping pets healthy"

"body" : "My quick brown fox eats rabbits on a regular basis"

查询如下:

"bool" : { "should" : [ {"match" : {"title" : "Brown fox" }}, { "match" : { "body" : "Brown fox"}}]}

es返回的结果应该是什么呢?哪一个doc的得分更高一些?是不是跟预期一致?

我们预期是希望doc2得分更高,因为相关性更强一些。

其实不然,es的返回结果是doc1得分更高。why?

看看es是如何计算score的吧。

(1)执行should中的两个match

(2)叠加score

(3)乘以match到的clause数目

(4)除以所有clause数目。

doc1中title和body中都存在brown,因此两个match都成功

doc2中显然只有一个match能成功

第三步中差距出来了,doc1是2,doc2则是1.因此doc1得分比doc2要高。

其实这不是我们想要的结果。这个例子中title和body是相互竞争的字段,我们想要的是最佳匹配,哪一个字段match的结果更好,就选用哪一个字段的score作为最终的score。

所以我们采用dis_max-query。意义是:返回match到任何一个子查询的doc,哪一个doc的match结果做好,作为最后的score。

“query” : { "dis_max" : { "queries" : [ {"match" : {"title" : "Brown fox"}}, {"match" : {"body" : "Brown fox"}}  ]}

现在,查询结果是我们想要的了。

新情况有出现了:如果我们沿用dis_max查询“quick pets”呢?

结果显示为doc1跟doc2是一样的score。原因在于:dis_max之选用单个最好匹配的score作为最后的score。

doc1中titile能匹配到quick,doc2中title能匹配到pets,body能匹配到brown,但是doc2的单个最佳匹配仍然只能得一分,跟doc1相同。

显然这也不是我们想要的结果,我们觉得doc2得分应该高一些?如何调节?我们需要综合考虑所有能match到的查询,同时还得考虑到最佳match的查询,因此tie_breaker参数出现了。配合tie_breaker参数,score的计算过程是专业的

(1)获取最佳匹配的score

(2)获取其他匹配的score,乘以tie_breaker

(3)两者相加,规范化,作为score值

tie_breaker的参数值要同时考虑到最佳match和所有match,推荐0.1---0.4,如果是0的话,就只考虑最佳match了

multi_match query提供了上边的机制,通过制定type实现相同的效果:best_fields, most_fields,cross_fields.默认是best_field。

如下dis_max的query:

“dis_max” : {

    "queries" : [

        {

           "match" : {

               "titile" : {

                      "query" : "Quick brown fox" , "minimun_should_match" : "30%"

                }

           }

        },

        {

            "match" : {

               "body" : {

                      "query" : "Quick brown fox" , "minimun_should_match" : "30%"

                }

           }

        }

    ],

    "tie_breaker" : 0.3

}

可以用下面的multi_match query代替:

{

    "multi_match" : {

        "query" : "Quick brown fox",

        "type" : "best_fields",

        "tie_breaker" : 0.3,

        "fields" : ["title" , "body"],

        "minimun_should_match" : "30%"

     }

}

fields字段支持通配符和单个字段提升boost(^)。

4:most_fields

es要提供的不仅仅是term-match的doc,相关的doc也要能提供出来,比如说近义词。当然,term-match的肯定要排在最前边。

一种解决方法就是同样的text,用不同的方式去索引,每一种方式都提供了不一样的相关性。

例如:

(1)用stemmer(词干)去作为主要的索引方式,提供最广阔的查询。比如“jumped” “jumping”都索引为jump。

(2)索引一些同义词,jump leap hop等。

(3)一些跟语言相关的特殊处理等

如果我们想搜索jumped,显然准确含有jumped的doc应该得分最高,而jumping则得分偏低。

如果采用上面的stemmer则得分是一样的。

常用的解决方案就是对动一个field采用不同的处理,其中一个会保存unstemmered version,其他处理则辅助这个version,提高相关度。

因此query的时候就是越多field能match到,doc得分越高。

基于以上分析,首先就要提供一种mapping的方式:

"properties" : {

    "title" : {

           "type" : "string",

           "analyzer" : "english",

           "fields" : {

                "std" : {

                     "type" : "string",

                     "analyzed" : "standard"

                }

           }

     }

}

两种不同的方式,分别用了不同的分词器。因此title字段跟title.std字段索引效果是不一样的。titile为主。

现在有两行记录:

{ "titile" : "My rabbit jumps"}  ---  doc1

{"title" : "Jumping jack rabbits"}  --- doc2

如果对titile字段,搜索jumping rabbits,doc的得分是一样的,词干一样。

如果对title.std字段,只有doc2能match。

如果我们想对所有字段做match,用一个bool-query做一个combine(没有用dis_max),这样doc1 doc2 都可以match,doc2得分更高一些。是不是更合理呢。

如下实现:

“query” : {

    "multi_match" : {

          "query" : "jumping rabbits",

          "type" : "most_fields",

          "fields" : [ "titile" , "title.std"]

     }

}

同样,也可以对字段设定boost。

结合上文理解一下dis_max和bool-query的score计算方式,更有助于理解most_fields和best_fields。

5:cross-field entity search

有一类实体存储,比如人名,地址等,不是存储在单一的field里边,人名可以分为first_name,last_name。地址有street, country, city等字段组成。这类实体查询的特殊性在于,每一个single query string都是跨越了多个字段(请仔细理解这句话)。前边用的most_field的方法并不能满足我们的要求,会带来错误的结果。原因有几个:

(1)most_field是用来找出最佳的field,这个field能满足最多的word的匹配,而不是跨越多个field去寻找最佳满足words的匹配。

(2)不能使用operator和minimum_should_match来减少相关性低的doc的长尾问题

(3)每一个field的tf是不一样的,相互之间交错会产生不好的排序结果。

6:field centric query

5中most_field问题的根本原因就在于:most_field是field centric的,目的就是寻找最佳匹配的field,而我们想要的是最佳匹配的term。

best_field也存在同样问题,他们都是field centric。我们来看看问题是如何产生的,如何解决之。

(1)在多个field中匹配到同样的word

most_field实质是生成了多个match子查询,每个子查询针对一个field,然后包装成一个bool查询。

因此,如果同一个word在多个field中都出现,相比,两个word同时在一个field中出现,谁得分更高?显然是第一个。比如:

“query” : "Poland Street W1V"

"type" : "most_field"

"fields" : [ "street", "city", "country", "postcode"]

执行过程不再赘述。如果doc1仅仅match到了Poland,但是在两个field中都match到了,而doc1却match到了Poland Street,但是仅仅在一个field中,doc1的score却高于doc2,这显然不是我们想要的结果。

(2)运用operator/minimun_should_match来解决长尾

显而易见,如果指定了operator=AND,没有一个doc可以match到,从语义上也是错误的。

(3)相关性

相关性计算就是两个因素:tf和idf。idf就是说如果一个term在同一个field中出现的越多次就说明越不重要,越少越重要。

现在我们查询Peter Smith,在first_name和last_name中。Peter是一个很普遍的first_name,smith是一个很普遍的last_name。因此两者都具有较低的idf。但是,如果我们有一个doc是Smith Williams?smitch可是一个不常见的first_name。但是返回结果呢?Smith Williams得分更高,但是我们要查询的是Peter Smitch啊???原因就在于Smith在first_name中的高idf已经压过了peter作为一个低idf出现在的first_name中和smith作为一个低idf出现在last_name中。

解决方案:

以上原因在于我们是在多个field中处理。因此我们只需要将多个field的信息整合成一个即可。

比如:生成一个full_name字段(可以用custom _all field的方法,mapping中设置copyto参数),包括first_name和last_name。我们针对full_name做查询,以上三个问题就不会出现。

但是,我们存储了冗余的信息。elasticsearch考虑到这一点,提出了另外一个解决方案,就是cross fields query。

7:cross fields query

对于上述问题,custom all是一种不过的解决方案,除了冗余信息以外,这种方案是在index过程中就要指定的,有没有在查询时的解决方法呢?es提供了cross field query的解决方案。

best field 和 most field是field centric的,而cross field是term centric的。这是最大的不同。cross field把multi field 看做一整个field,在这一个field里边去match 每一个term,相当于在多个field中match。

field centric的逻辑是这样的(operator=AND):

(+first_name Peter +first_name Smith)

(+last_name Peter +last_name Smith)

term centric的逻辑是这样的(operator=AND):

+(first_name Peter  last_name Peter)

+(last_name Smith  last_name Smith)

也就是说peter必须出现在其中一个字段中,同时smith也必须出现在其中一个字段中。

所以cross field其实是首先分析query string 产生一个 term list,针对每一个term在任何一个field中match。

因为6中问题1 问题2就解决了,问题3呢?

cross field 在field之间把inf混合起来,作为一个整体。

+blended(“peter” fields:[ "first_name", "last_name"])

+blended(“smith” fields:[ "first_name", "last_name"])

也就是说同时在first_name和last_name中查询smith的idf,取最小值。这样smith作为一个普通的last_name,也作为一个普通的first_name。

问题3解决。

注意一点:cross fields search中的field要使用同样的analyzer。如果使用不同的analyzed,则会按照analyzer对fields进行分组blended,工作模式类似于best field。

比如title使用了一个analyzer,first_name 和last_name 使用了另外一个analyzer,则

(+title peter +title smitch)

+blended(“peter” fields:[ "first_name", "last_name"])

+blended(“smith” fields:[ "first_name", "last_name"])

cross field相对于custom all的优势在于:可以在查询时指定每一个field的boost值。

8:exact value field

即not_analyzed的field。在muti_match query中混合使用not_analyzed的field和analyzed的field是无效的。避免在multi_match query中使用not_analyzed的field。

7中对analyzer的分析阐明了具体原因。












你可能感兴趣的:(search-in-depth)