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的分析阐明了具体原因。