共计 4499 个字符,预计需要花费 12 分钟才能阅读完成。
只有一个简单的 match 子句的查询是很少见的。我们经常需要在一个或者多个字段中查询相同的或者不同的查询字符串,意味着我们需要能够组合多个子查询以及使它们的相关性得分有意义。
1、best fields
假设我们有一个让用户搜索博客文章的网站。其中有两个文档如下:
PUT /test_index/_create/1
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
PUT /test_index/_create/2
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
进行查询:
GET /test_index/_search
{
"query": {
"bool": {
"should": [{ "match": { "title": "Brown fox"}},
{"match": { "body": "Brown fox"}}
]
}
}
}
输出结果:
{
"took" : 326,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.90425634,
"hits" : [
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.90425634,
"_source" : {
"title" : "Quick brown rabbits",
"body" : "Brown rabbits are commonly seen."
}
},
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.77041256,
"_source" : {
"title" : "Keeping pets healthy",
"body" : "My quick brown fox eats rabbits on a regular basis."
}
}
]
}
}
我们发现文档 1 的相关度分数更高,排在了前面。要理解原因,可以相像一下 bool 查询是如何计算相关度分数的
(1)运行 should 子句中的两个查询
(2)相加查询返回的分值
(3)将相加得到的分值乘以匹配的查询子句的数量
(4)除以总的查询子句的数量
文档 1 在两个字段中都包含了 brown,因此两个 match 查询都匹配成功并拥有了一个分值。文档 2 在 body 字段中包含了 brown 以及 fox,但是在 title 字段中没有出现任何搜索的单词。因此对 body 字段查询得到的高分加上对 title 字段查询得到的零分,然后在乘以匹配的查询子句数量 1,最后除以总的查询子句数量 2,导致整体分数值比文档 1 的低。
解决方法:
相比使用 bool 查询,我们可以使用 dis_max 查询(Disjuction Max Query),意思就是返回匹配了任何查询的文档,并且分值是产生了最佳匹配的查询所对应的分值:
GET /test_index/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {"title": "Brown fox"}
},
{
"match": {"body": "Brown fox"}
}
]
}
}
}
输出结果:
{
"took" : 11,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.77041256,
"hits" : [
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.77041256,
"_source" : {
"title" : "Keeping pets healthy",
"body" : "My quick brown fox eats rabbits on a regular basis."
}
},
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.6931472,
"_source" : {
"title" : "Quick brown rabbits",
"body" : "Brown rabbits are commonly seen."
}
}
]
}
}
此时就得到了我们想要的结果,文档 2 排在了文档 1 的前面
2、针对 best fields 查询进行调优
上面的例子中,假设我们搜索的是“quick pets”,两份文档中都包含了单词 quick, 但是只有文档 2 包含了 pets。两份文档都没能在一个字段中同时包含搜索的两个单词。
进行查询:
GET /test_index/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {"title": "Quick pets"}
},
{
"match": {"body": "Quick pets"}
}
]
}
}
}
输出结果:
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.6931472,
"hits" : [
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.6931472,
"_source" : {
"title" : "Quick brown rabbits",
"body" : "Brown rabbits are commonly seen."
}
},
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.6931472,
"_source" : {
"title" : "Keeping pets healthy",
"body" : "My quick brown fox eats rabbits on a regular basis."
}
}
]
}
}
此时可以发现两份文档的分值是一样的。但是我们期望的是同时匹配了 title 字段和 body 字段的文档能够拥有更高的排名。
注意:dis_max 查询只是简单的使用最佳匹配查询子句得到的相关度分数。
解决方法:
要想得到我们期望的结果,此时可以通过指定 tie_breaker 参数:
查询如下:
GET /test_index/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {"title": "Quick pets"}
},
{
"match": {"body": "Quick pets"}
}
],
"tie_breaker": 0.3
}
}
}
输出结果:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.87613803,
"hits" : [
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.87613803,
"_source" : {
"title" : "Keeping pets healthy",
"body" : "My quick brown fox eats rabbits on a regular basis."
}
},
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.6931472,
"_source" : {
"title" : "Quick brown rabbits",
"body" : "Brown rabbits are commonly seen."
}
}
]
}
}
此时就是我们期望的结果了,文档 2 的分数比文档 1 的要高了
tie_breaker 参数会让 dis_max 查询的行为更像是 dis_max 和 bool 的一种折中。它会通过下面的方式改变分值计算过程:
(1)取得最佳匹配查询子句的_score
(2)将其它每个匹配的子句的分值乘以 tie_breaker
(3)将以上得到的分值进行累加并规范化
通过 tie_breaker 参数匹配的所有子句都会起作用,只不过最佳匹配子句的作用更大
注意:tie_breaker 的取值范围是 0 到 1 之间的浮点数,取 0 时即为仅使用最佳匹配子句。取 1 则会将所有匹配的子句一视同仁。它的确切值需要根据你的数据和查询进行调整,但是一个合理的值会靠近 0,来确保不会压倒 dis_max 查询具有的最佳匹配性质。
3、multi_match 查询
multi_match 查询提供了一个简便的方法用来对多个字段执行相同的查询。
默认情况下,该查询以 best_fields 类型执行,它会为每个字段生成一个 match 查询,然后将这些查询包含在一个 dis_max 查询中。下面的 dis_max 查询:
GET /test_index/_search
{
"query": {
"dis_max": {
"tie_breaker": 0.3,
"queries": [
{
"match": {
"title": {
"query": "Quick brown fox",
"minimum_should_match": "30%"
}
}
},
{
"match": {
"body": {
"query": "Quick brown fox",
"minimum_should_match": "30%"
}
}
}
]
}
}
}
可以通过 multi_match 简单地重写如下:
GET /test_index/_search
{
"query": {
"multi_match": {
"query": "Quick brown fox",
"type": "best_fields",
"fields": ["title", "body"],
"tie_breaker": 0.3,
"minimum_should_match": "30%"
}
}
}
注意:type 属性为 best_fields;minimum_should_match 和 operator 参数会被传入到生成的 match 查询中
针对 multi_match,还可以使用通配符匹配字段名,以及针对个别字段进行加权
通配符:
"fields": "*_title"
加权:
"fields": ["*_title", "chapter_title^2"] # 此时 chapter_title 字段的 boost 值为 2,而 book_title 和 section_title 字段的 boost 值为默认的 1