- 1. 多字符串查询
- 2. 单字符串查询
- 3. 最佳字段策略实现多字段查询
- 4. 最佳字段查询调优
- 5. multi_match实现最佳字段查询
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "War and Peace" }},
{ "match": { "author": "Leo Tolstoy" }}
]
}
}
}
bool 查询采取more-matches-is-better匹配越多越好的方式,所以每条 match 语句的评分结果会被加在一起,从而为每个文档提供最终的分数 _score 。能与两条语句同时匹配的文档比只与一条语句匹配的文档得分要高。
2. 单字符串查询bool 查询是多语句查询的主干。它的适用场景很多,特别是当需要将不同查询字符串映射到不同字段的时候。问题在于,目前有些用户期望将所有的搜索项堆积到单个字段中,并期望应用程序能为他们提供正确的结果。
对于多词(multiword)、多字段(multifield)查询来说,不存在简单的万能方案。当用户输入了单个字符串查询的时候,通常会遇到以下3种情形:
1、最佳字段(best_fields):
当搜索词语具体概念的时候,比如 “brown fox” ,词组比各自独立的单词更有意义。文档在相同字段中包含的词越多越好,评分也来自于最匹配字段。
2、 多数字段(most_fields):
为了对相关度进行微调,常用的一个技术就是将相同的数据索引到不同的字段,它们各自具有独立的分析链。一个字段可以包括未经词干提取过的原词,另一个字段包括其他词源、口音。
主字段可能包括它们的词源、同义词以及变音词或口音词,被用来匹配尽可能多的文档。相同的文本被索引到其他字段,以提供更精确的匹配。其他字段是作为匹配每个文档时提高相关度评分的信号, 匹配字段越多则越好。
3、混合字段(cross_fields):
对于某些实体,我们需要在多个字段中确定其信息,单个字段都只能作为整体的一部分:
- Person: first_name 和 last_name (人:名和姓)
- Book: title 、 author 和 description (书:标题、作者、描述)
- Address: street 、 city 、 country 和 postcode (地址:街道、市、国家和邮政编码)
在这种情况下,我们希望在任何这些列出的字段中找到尽可能多的词,这有如在一个大字段中进行搜索,这个大字段包括了所有列出的字段。
上述所有都是多词、多字段查询,但每个具体查询都要求使用不同策略。
3. 最佳字段策略实现多字段查询3.1 最佳字段策略
假设有个网站允许用户搜索博客的内容,以下面两篇博客内容文档为例:
PUT /blog/_doc/1
{
"title": "Quick brown rabbits",
"content": "Brown rabbits are commonly seen."
}
PUT /blog/_doc/2
{
"title": "Keeping pets healthy",
"content": "My quick brown fox eats rabbits on a regular basis."
}
用户输入词组Brown fox然后点击搜索按钮。事先,我们并不知道用户的搜索项是会在 title 还是在 content 字段中被找到,但是,用户很有可能是想搜索相关的词组。用肉眼判断,文档 2 的匹配度更高,因为它同时包括要查找的两个词。现在运行以下 bool 查询:
GET /blog/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "content": "Brown fox" }}
]
}
}
}
"hits" : [
{
"_index" : "blog",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.90425634,
"_source" : {
"title" : "Quick brown rabbits",
"content" : "Brown rabbits are commonly seen."
}
},
{
"_index" : "blog",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.77041256,
"_source" : {
"title" : "Keeping pets healthy",
"content" : "My quick brown fox eats rabbits on a regular basis."
}
}
]
但是我们发现查询的结果是文档 1 的评分更高,为了理解导致这样的原因,需要回想一下 bool 是如何计算评分的:
- 它会执行 should 语句中的两个查询。
- 加和两个查询的评分。
- 乘以匹配语句的总数。
- 除以所有语句总数(这里为:2)。
① 简单计算文档1的评分:
文档 1 的两个字段都包含 brown 这个词,所以两个 match 语句都能成功匹配并且有一个评分:
{ "match": { "title": "Brown fox" }} //假如评分为1.2
{ "match": { "content":"Brown fox" }} //假如评分为1.1
两个分数的和为1.1 + 1.2 = 2.3,match query的总数为2,query的总数为2,因此文档1的评分为:2.3*2/2=2.3
② 简单计算文档2的评分:
文档 2 的 content 字段同时包含 brown 和 fox 这两个词,但 title 字段没有包含任何词:
{ "match": { "title": "Brown fox" }} //评分为0
{ "match": { "content":"Brown fox" }} //假如评分为2.4
两个分数的和为0 + 2.3 = 2.3,match query的总数为1,query的总数为2,因此文档1的评分为:2.4*1/2=1.2因此文档2比文档1有更低的评分。
如果不是简单将每个字段的评分结果加在一起,而是将最佳匹配字段的评分作为查询的整体评分,结果会怎样?
这样返回的结果可能是:同时包含 brown 和 fox 的单个字段比反复出现相同词语的多个不同字段有更高的相关度。比如文档2中content字段的评分最高为2.4,而文档1中title字段的评分最高为1.2,此时就会使得文档2的整体评分为2.4比文档1的整体评分1.2要高。要想解决这个问题就需要使用dis_max 查询。
3.2 dis_max 查询
不使用 bool 查询,可以使用 dis_max 即分离最大化查询 。分离最大化查询指的是:将任何与任一查询匹配的文档作为结果返回,但只将最佳匹配的评分作为查询的评分结果返回:
GET /blog/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "content": "Brown fox" }}
]
}
}
}
得到我们想要的结果为:
"hits" : [
{
"_index" : "blog",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.77041256,
"_source" : {
"title" : "Keeping pets healthy",
"content" : "My quick brown fox eats rabbits on a regular basis."
}
},
{
"_index" : "blog",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.6931472,
"_source" : {
"title" : "Quick brown rabbits",
"content" : "Brown rabbits are commonly seen."
}
}
]
4. 最佳字段查询调优
4.1 最佳字段策略查询调优
PUT /blog/_doc/1
{
"title": "Quick brown rabbits",
"content": "Brown rabbits are commonly seen."
}
PUT /blog/_doc/2
{
"title": "Keeping pets healthy",
"content": "My quick brown fox eats rabbits on a regular basis."
}
当用户搜索 quick pets 时会发生什么呢?在前面的例子中,两个文档都包含词 quick ,但是只有文档 2 包含词 pets ,两个文档中都不具有同时包含两个词的相同字段。
如下,一个简单的 dis_max 查询会采用单个最佳匹配字段,而忽略其他的匹配:
"hits" : [
{
"_index" : "blog",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.6931472, // 分数 0.6931472
"_source" : {
"title" : "Quick brown rabbits",
"content" : "Brown rabbits are commonly seen."
}
},
{
"_index" : "blog",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.6931472, // 分数 0.6931472
"_source" : {
"title" : "Keeping pets healthy",
"content" : "My quick brown fox eats rabbits on a regular basis."
}
}
]
文档2同时匹配 title 和 content 字段,而文档1只有一个字段title匹配,但两个文档的评分是完全相同的。
我们可能期望同时匹配 title 和 content 字段的文档比只与一个字段匹配的文档的相关度更高,但事实并非如此,因为 dis_max 查询只会简单地使用单个最佳匹配语句的评分 _score 作为整体评分。为了解决这个问题,可以指定 tie_breaker 这个参数。
4.2 tie_breaker 参数
可以通过指定 tie_breaker 这个参数将其他匹配语句的评分也考虑其中:
GET /blog/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "content": "Quick pets"}}
],
"tie_breaker": 0.3
}
}
}
结果如下:
"hits" : [
{
"_index" : "blog",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.87613803, // 文档2的评分增加了
"_source" : {
"title" : "Keeping pets healthy",
"content" : "My quick brown fox eats rabbits on a regular basis."
}
},
{
"_index" : "blog",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.6931472, // 文档1的评分仍然为 0.6931472
"_source" : {
"title" : "Quick brown rabbits",
"content" : "Brown rabbits are commonly seen."
}
}
]
tie_breaker 参数提供了一种 dis_max 和 bool 之间的折中选择,它的评分方式如下:
- 获得最佳匹配语句的评分 _score 。
- 将其他匹配语句的评分结果与 tie_breaker 相乘。
- 对以上评分求和并规范化。
有了 tie_breaker ,会考虑所有匹配语句,但最佳匹配语句依然占最终结果里的很大一部分。
tie_breaker 可以是 0 到 1 之间的浮点数,其中 0 代表使用 dis_max 最佳匹配语句的普通逻辑, 1 表示所有匹配语句同等重要。最佳的精确值需要根据数据与查询调试得出,但是合理值应该与零接近(处于 0.1 - 0.4 之间),这样就不会颠覆 dis_max 最佳匹配性质的根本。
5. multi_match实现最佳字段查询5.1 multi_match查询
multi_match 查询为能在多个字段上反复执行相同查询提供了一种便捷方式。
multi_match 多匹配查询的类型有多种,其中的三种与我们介绍的三个场景对应,即: best_fields 、 most_fields 和 cross_fields (最佳字段、多数字段、跨字段)。
默认情况下,查询的类型是 best_fields ,这表示它会为每个字段生成一个 match 查询,然后将它们组合到 dis_max 查询的内部,如下:
GET /blog/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {
"title": {
"query": "Quick brown fox",
"minimum_should_match": "30%" // 查询关键词中必须匹配30%才可
}
}
},
{
"match": {
"content": {
"query": "Quick brown fox",
"minimum_should_match": "30%"
}
}
}
],
"tie_breaker": 0.3
}
}
}
minimum_should_match,主要是用来去长尾的,比如你搜索5个关键词,但是很多结果是只匹配1个关键词的,其实跟你想要的结果相差甚远,这些结果就是长尾。minimum_should_match可以控制搜索结果的精准度,只有匹配一定数量的关键词的数据,才能返回。
上面这个查询用 multi_match 重写成更简洁的形式:
GET /blog/_search
{
"query": {
"multi_match": {
"query": "Quick brown fox",
"type": "best_fields",
"fields": [ "title", "content" ],
"tie_breaker": 0.3,
"minimum_should_match": "30%"
}
}
}
5.2 查询字段名称的模糊匹配
字段名称可以用模糊匹配的方式给出:任何与模糊模式正则匹配的字段都会被包括在搜索条件中,例如可以使用以下方式同时匹配 book_title 、 chapter_title 和 section_title (书名、章名、节名)这三个字段:
{
"multi_match": {
"query": "Quick brown fox",
"fields": "*_title"
}
}
5.3 提升单个字段的权重
可以使用 ^ 字符语法为单个字段提升权重,在字段名称的末尾添加 ^boost ,其中 boost 是一个浮点数:
{
"multi_match": {
"query": "Quick brown fox",
"fields": [ "*_title", "chapter_title^2" ]
}
}
chapter_title 这个字段的 boost 值为 2 ,而其他两个字段 book_title 和 section_title 字段的默认 boost 值为 1



