查询很少是对一个字段做 match 查询,通常都是一个 query 查询多个字段,比如一个 doc 有 title、content、pagetag 等文本字段,要在这些字段查询含多个 term 的 query,就要对它们的相关度评分做合理的合并。这被称为多词(multiword)、多字段(multifield)查询。

如果一个 query 可以结构化,如哪些词是 title,哪些词是 author,那么就可以直接在相关字段中查询,使用 bool 查询即可解决问题,bool 查询是“匹配越多越好”,如搜“War and Peace Leo Tolstoy”,查询语句如下:

GET /_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title":  "War and Peace" }},
        { "match": { "author": "Leo Tolstoy"   }}
      ]
    }
  }
}

还可以对不同的字段加不同的 boost 权重。

以上被称为多重查询字符串,也可算是结构化查询,不过现实中通常是一个 query 在多个字段中查询,即单一查询字符串。毕竟对 query 做结构化需要些 nlp 技术和额外的人力成本,且比起单一查询字符串的效果提升也有限,所以若不是对召回效果有更高追求,还是不要轻举妄动,就好好做一个 query 在多个字段的查询吧。

一个 query 在多个字段中的查询,有三种策略:best_fields、most_fields、cross_fields。

介绍这三种策略之前,先铺垫下布尔查询和 dis_max 查询。

  1. bool 查询

一个 query 在多个字段中的查询,同样可使用 bool 查询。

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

不过由于 bool 查询评分公式的问题,效果不太好,比如一个文档 title 和 body 都包含 brown,不包含 fox,另一个文档在 body 字段包含了 brown 和 fox,显然后者更符合搜索意图,但 bool 查询的评分前者高,为了理解导致这样的原因,需要看下 bool 查询是如何计算评分的:

它会执行 should 语句中的两个查询。
加和两个查询的评分。
乘以匹配字段的总数(这里不知是否理解正确,存疑,待验证)。
除以所有语句总数(这里为:2)。

注意这里的“乘以匹配语句的总数”是关键,这会导致匹配字段越多,分值越大。(后面的 most_fields 也是使用这个计算,才使得匹配字段数越多,分值越大)

解决方案是,使用最佳匹配字段的分值作为整个查询的整体分值,让包含 query 两个单词的字段有更高的权重,而不是在不同的字段中重复出现的相同单词。dis_max 查询应运而生。

  1. dis_max

dis_max 查询就是返回匹配了 query 的文档,分值是最佳匹配字段产生的分值。加上 tie_breaker 可得出很好的搜索效果。

{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Quick pets" }},
                { "match": { "body":  "Quick pets" }}
            ],
            "tie_breaker": 0.3
        }
    }
}