1. 最佳字段
假设有个网站允许用户搜索博客的内容,以下面两篇博客内容文档为例:
PUT /my_index/my_type/1
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
PUT /my_index/my_type/2
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
此时用户搜索 " brown fox ",用肉眼判断文档二更匹配。 由于不知道该搜索词出现的字段,所以我们用 bool 查询进行查询。
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
但在返回的结果中文档 1 比 文档 2 的相关度高,因为搜索时,会将每个字段的相关度相加然后计算总评分,文档一的 title 和 body 中都包含 Brown, 所以评分较高,如果不是将每个字段的评分想加,而是将最佳匹配字段的评分作为查询的整体评分,返回的结果将是同时包含 brown 和 fox 的字段所在文档相关度比较高。
此时应该使用 dis_max 查询,而不是 bool 查询。最大化查询(Disjunction Max Query)指的是: 将任何与查询匹配的文档作为结果返回,但是每个文档的评分都是以最佳匹配的评分作为结果 ,而不是再进行计算。意思是该文档的评分是dis_max下所有查询的评分的最大值,不再进行求和平均计算。
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
由于dis_max会忽略其他匹配查询的分数,可以通过 tie_breaker
进行使得其他匹配的分数也参与到计算该文档的评分中。和权重boost
不同,权重是字段所占的权重,而tie_breaker
是查询所占的权重。
表示除了最佳匹配,次匹配所占总分比例的 30 %
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
],
"tie_breaker": 0.3
}
}
}
2. 多数字段
全文搜索被称作是 召回率(Recall) 与 精确率(Precision) 的战场: 召回率 ——返回所有的相关文档; 精确率 ——不返回无关文档。目的是在结果的第一页中为用户呈现最为相关的文档。
为了提高召回率的效果,我们扩大搜索范围——不仅返回与用户搜索词精确匹配的文档,还会返回我们认为与查询相关的所有文档。如果一个用户搜索 “quick brown box” ,一个包含词语 fast foxes 的文档被认为是非常合理的返回结果。如果有多个文档比该文档更匹配,则该文档出现的位置应该在这些文档之后。
提高全文相关性精度的常用方式是为同一文本建立多种方式的索引,每种方式都提供了一个不同的相关度信号 signal 。主字段会以尽可能多的形式的去匹配尽可能多的文档。比如我们搜索华为手机,在手机的 desc 字段使用默认分词器,而他的词根 ' 华为手机 ' 不分词。在搜索华为手机时,会将该文档作为结果返回,而他的词根用来提高该文档的相关度。
对我们的字段索引两次:一次使用词干模式以及一次非词干模式。为了做到这点,采用 multifields 来实现。
PUT /my_index
{
"settings": { "number_of_shards": 1 },
"mappings": {
"my_type": {
"properties": {
"title": {
"type": "string",
"analyzer": "english",
"fields": {
"std": {
"type": "string",
"analyzer": "standard"
}
}
}
}
}
}
}
上例中,给某个字段索引了两次,分别使用了不同的分词器,可以使用广度匹配字段用来匹配更多的数据,用来提升召回率,然后用该字段的词根来将相关度更高的文档置于顶部。
GET /index/_search
{
"query": {
"multi_match": {
"query": "jumping rabbits",
"type": "most_fields",
"fields": [ "title", "title.std^10" ]
}
}
}
跨字段实体搜索,比如人、地址等实体,需要用多个字段来唯一表示一个实体,(last_name、first_name),使用bool查询将会使代码过长,而是用多字段查询又不能完全符合题意。因为多字段搜索是为多数字段是否满足查询条件,不能在所有字段中找到最匹配的、搜索词在多个字段值的出现的频率不一样,会导致结果有误差。
一种解决方案是增加一个新的字段,比如full_name, 可以使用该字段进行对复杂实体的搜索,但是又会出现冗余数据。es给我们提供了两种解决方案,一个是在索引时,一个是在搜索时。
3. 混合字段
在之前说过, all_filed字段包括了该文档所有值的结合,但是这样并不灵活,我们可以通过copy_to
参数人为增加一个all字段,比如下列增加一个full_name
字段。
PUT /my_index
{
"mappings": {
"person": {
"properties": {
"first_name": {
"type": "string",
"copy_to": "full_name"
},
"last_name": {
"type": "string",
"copy_to": "full_name"
},
"full_name": {
"type": "string"
}
}
}
}
}
在索引时创建_all字段是一个方案,而es还在搜索时提供了另一种方案,使用 cross_fields 类型进行 multi_match 查询。 cross_fields 使用词中心式(term-centric)的查询方式,这与 best_fields 和 most_fields 使用字段中心式(field-centric)的查询方式非常不同。
字段中心式:搜索词必须同时出现在同一个字段中。
词中心式:搜索词必须同时出现,但可以在任意一个字段中。
GET /books/_search
{
"query": {
"multi_match": {
"query": "peter smith",
"type": "cross_fields",
"operator": "and",
"fields": [ "first_name", "last_name" ]
}
}
}