查询不多是对一个字段作 match 查询,一般都是一个 query 查询多个字段,好比一个 doc 有 title、content、pagetag 等文本字段,要在这些字段查询含多个 term 的 query,就要对它们的相关度评分作合理的合并。这被称为多词(multiword)、多字段(multifield)查询。html
若是一个 query 能够结构化,如哪些词是 title,哪些词是 author,那么就能够直接在相关字段中查询,使用 bool 查询便可解决问题,bool 查询是“匹配越多越好”,如搜“War and Peace Leo Tolstoy”,查询语句以下:curl
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "War and Peace" }},
{ "match": { "author": "Leo Tolstoy" }}
]
}
}
}
还能够对不一样的字段加不一样的 boost 权重。elasticsearch
以上被称为多重查询字符串,也可算是结构化查询,不过现实中一般是一个 query 在多个字段中查询,即单一查询字符串。毕竟对 query 作结构化须要些 nlp 技术和额外的人力成本,且比起单一查询字符串的效果提高也有限,因此若不是对召回效果有更高追求,仍是不要轻举妄动,就好好作一个 query 在多个字段的查询吧。ide
一个 query 在多个字段中的查询,有三种策略:best_fields、most_fields、cross_fields。ui
介绍这三种策略以前,先铺垫下布尔查询和 dis_max 查询。url
1. bool 查询spa
一个 query 在多个字段中的查询,一样可以使用 bool 查询。日志
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
不过因为 bool 查询评分公式的问题,效果不太好,好比一个文档 title 和 body 都包含 brown,不包含 fox,另外一个文档在 body 字段包含了 brown 和 fox,显而后者更符合搜索意图,但 bool 查询的评分前者高,为了理解致使这样的缘由,须要看下 bool 查询是如何计算评分的:code
它会执行 should 语句中的两个查询。htm
加和两个查询的评分。
乘以匹配字段的总数(这里不知是否理解正确,存疑,待验证)。
除以全部语句总数(这里为:2)。
注意这里的“乘以匹配语句的总数”是关键,这会致使匹配字段越多,分值越大。(后面的 most_fields 也是使用这个计算,才使得匹配字段数越多,分值越大)
解决方案是,使用最佳匹配字段的分值做为整个查询的总体分值,让包含 query 两个单词的字段有更高的权重,而不是在不一样的字段中重复出现的相同单词。dis_max 查询应运而生。
2. dis_max
dis_max 查询就是返回匹配了 query 的文档,分值是最佳匹配字段产生的分值。加上 tie_breaker 可得出很好的搜索效果。
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
],
"tie_breaker": 0.3
}
}
}
3. best_fields
multi_match 查询提供了一个简便的方法对多个字段执行相同的查询。默认状况下,该查询以 best_fields 类型执行,它会为每一个字段生成一个 match 查询,而后将这些查询包含在一个 dis_max 查询中。
例如:
GET /_search
{
"query": {
"multi_match" : {
"query": "brown fox",
"type": "best_fields",
"fields": [ "subject", "message" ],
"tie_breaker": 0.3
}
}
}
执行时就变成了:
GET /_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "subject": "brown fox" }},
{ "match": { "message": "brown fox" }}
],
"tie_breaker": 0.3
}
}
}
可经过 caret 语法(^) 对个别字段加权,如:
{
"multi_match": {
"query": "Quick brown fox",
"fields": [ "title", "chapter_title^2" ]
}
}
best_fields 和 most_fields 都是以字段为中心的查询,参数 operator 和 minimum_should_match 也是针对每一个字段生效的,至少有一个字段知足要求,才会经过筛选并进入下一步计分,计分时也只有符合要求的字段才会参与计分。
operator 默认为 or,若是设置为 and,那么字段必须匹配全部 query 分词。当 operator 设为默认值 or 时,minimum_should_match 才会生效,设置每一个字段应匹配分词数。
因此有些 query 信息是分布在多个字段上的,这时就不适合设置 operator 为 and,会减小召回量。若是确认 query 信息必定彻底在某个字段上,则可设为 and。
为与 cross_fields 作对比,这里举个实际应用的例子。
搜索词为“苹果8plus国行”,文档有三个字段:cateName、title、content,其中 cateName 和 content 用 ik_smart 分词,title 用 ik_max_word 分词(不一样字段的分词方法差别会在 cross_fields 中有所体现)。
看下 best_fields 查询的实际执行。
ES 语句:
curl -XGET 'ip:port/indexname/_validate/query?explain&pretty' -d' { "query": { "function_score": { "query": { "bool": { "should": [{ "multi_match": { "query": "苹果8plus国行", "fields": ["cate_name^1.0", "content^1.0", "title^1.0"], "type": "best_fields", "operator": "AND", "tie_breaker": 0.3 } }] } } } } } '
返回解释:
(
(+cate_name:苹果8 +cate_name:plus +cate_name:国行) |
(+content:苹果8 +content:plus +content:国行) |
(+title:苹果8 +title:苹果 +title:8plus +title:8 +title:plus +title:国 +title:行)
)~0.3
明显的以字段为中心的查询。
tips:字段名能够经过通配符指定,如:
{
"multi_match": {
"query": "Quick brown fox",
"fields": "*_title"
}
}
4. most_fields
有时为了尽量多地匹配文档,会将同一文本的不一样形式索引到多个字段。
ES语句(注意不要加 operator 或 minimum_should_match,否则就跟 best_fields 同样了):
curl -XGET 'ip:port/indexname/_validate/query?explain&pretty' -d' { "query": { "function_score": { "query": { "bool": { "should": [{ "multi_match": { "query": "苹果8plus国行", "fields": ["cate_name^1.0", "content^1.0", "title^1.0"], "type": "most_fields" } }] } } } } } '
返回解释:
(
(cate_name:苹果8 cate_name:plus cate_name:国 cate_name:行) |
(content:苹果8 content:plus content:国 content:行) |
(title:苹果8 title:苹果 title:8plus title:8 title:plus title:国 title:行)
)~1.0
根据文档,most_fields 查询是用 bool 查询将两个字段语句包在里面,而不是像 best_fields 同样用 dis_max。(不知这个怎么验证,在本身的 ES 里试了下,看 explain 日志,没看出跟 best_fields 时 tie_breaker=1 有什么差异)
5. cross_fields
ES语句:
curl -XGET 'ip:port/indexname/_validate/query?explain&pretty' -d' { "query": { "function_score": { "query": { "bool": { "should": [{ "multi_match": { "query": "苹果8plus国行", "fields": ["cate_name^1.0", "content^1.0", "title^1.0"], "type": "cross_fields", "operator": "AND", "tie_breaker": 0.3 } }] } } } } } '
返回解释:
(
(+blended(terms:[cate_name:苹果8, content:苹果8]) +
blended(terms:[cate_name:plus, content:plus]) +
blended(terms:[cate_name:国行])) |
(+title:苹果8 +title:苹果 +title:8plus +title:8 +title:plus +title:国 +title:行)
)~0.3
这里 title 要和 cate_name、content 分开计算的缘由,是由于两部分的分词方法不一样,term 也不一样。
根据《Elasticsearch: 权威指南》,关于 苹果8 的 IDF,会在 cate_name 和 content 中取最小值做为两个字段的 IDF。(待验证)
参考资料