MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

ElasticSearch搜索API的高级用法

2022-03-314.4k 阅读

ElasticSearch搜索API的多字段搜索

在实际应用中,我们常常需要在多个字段上进行搜索,以获取更全面准确的结果。ElasticSearch提供了强大的多字段搜索功能,其中multi_match查询是实现这一功能的关键。

基本的multi_match查询

假设我们有一个包含书籍信息的索引,每个文档有title(标题)和description(描述)字段。我们想要搜索同时在标题和描述中包含特定关键词的书籍。以下是使用multi_match查询的基本示例:

{
    "query": {
        "multi_match": {
            "query": "人工智能",
            "fields": ["title", "description"]
        }
    }
}

在上述示例中,query指定了我们要搜索的关键词“人工智能”,fields数组指定了要在哪些字段上进行搜索。

multi_match的匹配类型

multi_match支持多种匹配类型,不同的匹配类型会影响搜索结果的相关性和准确性。

  1. best_fields:这是默认的匹配类型。它会在指定的多个字段中寻找最佳匹配,优先考虑单个字段中最相关的匹配结果。例如,如果在title字段中匹配度很高,即使description字段匹配度较低,该文档也可能排在前面。
{
    "query": {
        "multi_match": {
            "query": "人工智能",
            "fields": ["title", "description"],
            "type": "best_fields"
        }
    }
}
  1. most_fields:这种类型会尝试在尽可能多的字段中找到匹配项。它会为每个字段计算相关性分数,并将这些分数合并,因此更注重整体的匹配情况。如果一个文档在多个字段中有较弱的匹配,可能会比在单个字段中有强匹配但其他字段无匹配的文档排名更高。
{
    "query": {
        "multi_match": {
            "query": "人工智能",
            "fields": ["title", "description"],
            "type": "most_fields"
        }
    }
}
  1. cross_fields:当你希望将多个字段视为一个大的文本块进行搜索时,可以使用cross_fields类型。它假设所有字段都包含相关信息,并且将所有字段合并起来查找匹配项。例如,在搜索人名时,如果一个字段包含名字,另一个字段包含姓氏,cross_fields可以帮助找到相关结果。
{
    "query": {
        "multi_match": {
            "query": "张三",
            "fields": ["first_name", "last_name"],
            "type": "cross_fields"
        }
    }
}

提升特定字段的权重

在多字段搜索中,我们可能希望某些字段比其他字段更重要,即赋予这些字段更高的权重。例如,在书籍搜索中,title字段可能比description字段更重要。我们可以通过在fields数组中为字段指定权重来实现这一点。

{
    "query": {
        "multi_match": {
            "query": "人工智能",
            "fields": ["title^3", "description"]
        }
    }
}

在上述示例中,title^3表示title字段的权重是description字段的3倍。这样,在计算相关性分数时,title字段的匹配对整体分数的贡献会更大。

ElasticSearch搜索API的嵌套查询

当文档结构包含嵌套对象时,我们需要使用嵌套查询来准确搜索嵌套在其中的数据。嵌套查询允许我们在嵌套文档的层面上进行查询,而不仅仅是在顶层文档。

嵌套文档结构示例

假设我们有一个电商产品索引,每个产品文档包含产品基本信息以及多个评论,评论是嵌套在产品文档中的。

{
    "product_name": "手机",
    "price": 5999,
    "reviews": [
        {
            "author": "用户A",
            "rating": 4,
            "comment": "性能不错"
        },
        {
            "author": "用户B",
            "rating": 3,
            "comment": "外观还行"
        }
    ]
}

基本的嵌套查询

要搜索评论中包含特定关键词的产品,我们可以使用以下嵌套查询:

{
    "query": {
        "nested": {
            "path": "reviews",
            "query": {
                "match": {
                    "reviews.comment": "性能不错"
                }
            }
        }
    }
}

在上述示例中,path指定了嵌套对象的路径,即reviewsquery部分是在嵌套文档内部执行的实际查询,这里是在reviews.comment字段中匹配“性能不错”。

嵌套查询的相关性分数

嵌套查询的一个重要方面是相关性分数的计算。默认情况下,ElasticSearch会将嵌套查询的相关性分数合并到顶层文档的分数中。但是,由于嵌套文档与顶层文档之间的关系,分数计算可能会有些复杂。

例如,如果我们有多个嵌套评论,并且不同评论的匹配程度不同,ElasticSearch会根据匹配的嵌套文档的分数以及嵌套文档与顶层文档的关系来计算最终的顶层文档分数。可以通过调整score_mode参数来控制分数的合并方式。

  1. avg:这是默认的score_mode。它会计算所有匹配的嵌套文档分数的平均值,并将其作为顶层文档的相关分数。
{
    "query": {
        "nested": {
            "path": "reviews",
            "query": {
                "match": {
                    "reviews.comment": "性能不错"
                }
            },
            "score_mode": "avg"
        }
    }
}
  1. max:选择所有匹配的嵌套文档中分数最高的作为顶层文档的相关分数。
{
    "query": {
        "nested": {
            "path": "reviews",
            "query": {
                "match": {
                    "reviews.comment": "性能不错"
                }
            },
            "score_mode": "max"
        }
    }
}
  1. sum:将所有匹配的嵌套文档分数相加作为顶层文档的相关分数。
{
    "query": {
        "nested": {
            "path": "reviews",
            "query": {
                "match": {
                    "reviews.comment": "性能不错"
                }
            },
            "score_mode": "sum"
        }
    }
}

ElasticSearch搜索API的聚合查询

聚合查询是ElasticSearch的一个强大功能,它允许我们对搜索结果进行统计分析。通过聚合,我们可以计算文档的数量、平均值、最大值、最小值等,还可以进行分组统计。

简单的聚合示例 - 文档数量统计

假设我们有一个包含文章的索引,我们想要统计文章的总数。可以使用以下聚合查询:

{
    "aggs": {
        "article_count": {
            "value_count": {
                "field": "id"
            }
        }
    }
}

在上述示例中,aggs表示聚合部分。article_count是我们给这个聚合操作起的名字,可以自定义。value_count是聚合类型,这里用于统计id字段的非空值数量,从而得到文章总数。

分组聚合 - 按类别统计文章数量

如果文章文档包含category(类别)字段,我们想要按类别统计文章数量,可以使用以下查询:

{
    "aggs": {
        "category_count": {
            "terms": {
                "field": "category"
            }
        }
    }
}

这里terms聚合类型会根据category字段的值进行分组,并统计每个组中的文档数量。返回结果会包含每个类别以及该类别下的文章数量。

多层聚合 - 按类别统计文章平均阅读量

假设文章文档还包含read_count(阅读量)字段,我们不仅要按类别统计文章数量,还要计算每个类别下文章的平均阅读量。可以通过多层聚合来实现:

{
    "aggs": {
        "category_stats": {
            "terms": {
                "field": "category"
            },
            "aggs": {
                "avg_read_count": {
                    "avg": {
                        "field": "read_count"
                    }
                }
            }
        }
    }
}

在上述示例中,外层的terms聚合按category进行分组。内层的avg聚合在每个类别组内计算read_count的平均值。这样我们就能得到每个类别下文章的平均阅读量。

过滤聚合 - 仅统计特定条件下的文章

有时候我们只想对满足特定条件的文章进行聚合。例如,只统计阅读量大于100的文章的类别分布。可以在聚合查询中添加过滤条件:

{
    "query": {
        "range": {
            "read_count": {
                "gt": 100
            }
        }
    },
    "aggs": {
        "category_count": {
            "terms": {
                "field": "category"
            }
        }
    }
}

上述查询中,query部分首先过滤出阅读量大于100的文章,然后aggs部分对这些过滤后的文章按category进行分组统计。

ElasticSearch搜索API的地理空间查询

随着基于位置的应用越来越多,ElasticSearch提供了强大的地理空间查询功能,允许我们根据地理位置信息进行搜索。

地理点数据类型

在ElasticSearch中,我们使用geo_point数据类型来存储地理坐标(经度和纬度)。例如,我们有一个包含店铺信息的索引,每个店铺文档包含其地理位置:

{
    "store_name": "店铺A",
    "location": {
        "lat": 30.5,
        "lon": 120.3
    }
}

距离查询 - 查找附近的店铺

要查找距离某个特定位置一定距离内的店铺,可以使用距离查询。例如,查找距离坐标(30.6, 120.4)10公里内的店铺:

{
    "query": {
        "geo_distance": {
            "distance": "10km",
            "location": {
                "lat": 30.6,
                "lon": 120.4
            },
            "field": "location"
        }
    }
}

在上述示例中,distance指定了距离范围,location是中心点坐标,field指定了存储地理坐标的字段。

地理边界查询 - 查找在多边形内的店铺

如果我们想查找在某个多边形区域内的店铺,可以使用地理边界查询。假设我们定义了一个多边形区域,由多个坐标点组成:

{
    "query": {
        "geo_polygon": {
            "location": {
                "points": [
                    {
                        "lat": 30.4,
                        "lon": 120.2
                    },
                    {
                        "lat": 30.6,
                        "lon": 120.2
                    },
                    {
                        "lat": 30.6,
                        "lon": 120.4
                    },
                    {
                        "lat": 30.4,
                        "lon": 120.4
                    }
                ]
            }
        }
    }
}

上述查询会查找location字段的坐标位于定义的多边形内的店铺。

地理形状查询 - 更复杂的地理形状匹配

除了多边形,ElasticSearch还支持其他地理形状的查询,如圆形、矩形等。例如,使用圆形地理形状查询查找以某个点为圆心,一定半径内的店铺:

{
    "query": {
        "geo_shape": {
            "location": {
                "shape": {
                    "type": "circle",
                    "coordinates": [120.3, 30.5],
                    "radius": "5km"
                },
                "relation": "within"
            }
        }
    }
}

在这个示例中,type指定了地理形状为圆形,coordinates是圆心坐标,radius是半径。relation指定了匹配关系为“within”,即查找位于圆形内的店铺。

ElasticSearch搜索API的排序和分页

在搜索结果较多时,排序和分页是非常重要的功能,它可以帮助我们更好地展示和处理数据。

基本排序

假设我们有一个包含商品的索引,每个商品文档有price(价格)字段。我们想要按价格升序显示商品,可以使用以下查询:

{
    "sort": [
        {
            "price": {
                "order": "asc"
            }
        }
    ]
}

如果要按价格降序排序,只需将order改为desc

{
    "sort": [
        {
            "price": {
                "order": "desc"
            }
        }
    ]
}

多字段排序

有时候我们需要根据多个字段进行排序。例如,先按价格升序排序,如果价格相同,再按销量降序排序。假设商品文档还有sales_count(销量)字段:

{
    "sort": [
        {
            "price": {
                "order": "asc"
            }
        },
        {
            "sales_count": {
                "order": "desc"
            }
        }
    ]
}

分页

ElasticSearch使用fromsize参数来实现分页。from表示从结果集的第几个文档开始返回,size表示返回的文档数量。例如,要获取第11到20条商品记录:

{
    "from": 10,
    "size": 10
}

需要注意的是,当from值较大时,查询性能可能会下降,因为ElasticSearch需要从所有匹配结果中跳过from个文档。在这种情况下,可以考虑使用滚动(scroll)API来处理大量数据的分页。

ElasticSearch搜索API的脚本查询

脚本查询允许我们使用自定义的脚本逻辑来进行搜索和计算相关性分数。这在一些复杂的业务场景中非常有用。

使用脚本进行字段计算

假设我们有一个包含员工信息的索引,每个员工文档有salary(工资)和bonus(奖金)字段。我们想要查找工资和奖金总和大于某个值的员工。可以使用脚本查询:

{
    "query": {
        "script": {
            "script": {
                "source": "doc['salary'].value + doc['bonus'].value > params.threshold",
                "params": {
                    "threshold": 10000
                }
            }
        }
    }
}

在上述示例中,source部分是实际执行的脚本,它计算salarybonus字段值的总和,并与params中定义的threshold进行比较。

使用脚本自定义相关性分数

有时候默认的相关性计算方式不能满足业务需求,我们可以通过脚本自定义相关性分数。例如,在搜索文章时,我们希望根据文章的发布时间和点赞数来综合计算相关性分数。假设文章文档有publish_date(发布时间)和like_count(点赞数)字段:

{
    "query": {
        "function_score": {
            "query": {
                "match_all": {}
            },
            "functions": [
                {
                    "script_score": {
                        "script": {
                            "source": "double age = (System.currentTimeMillis() - doc['publish_date'].value.getMillis()) / (1000 * 60 * 60 * 24 * 365); return doc['like_count'].value / age + 1",
                            "lang": "painless"
                        }
                    }
                }
            ]
        }
    }
}

在上述示例中,function_score用于通过脚本计算相关性分数。script_score中的脚本根据文章的发布时间计算其“年龄”,并结合点赞数来计算一个综合的分数,从而影响搜索结果的排序。

ElasticSearch搜索API的高亮显示

高亮显示可以帮助用户在搜索结果中快速定位到关键词所在的位置,提高用户体验。

基本的高亮显示

假设我们有一个包含新闻文章的索引,文章内容存储在content字段中。我们想要搜索包含“科技进展”关键词的文章,并对关键词进行高亮显示:

{
    "query": {
        "match": {
            "content": "科技进展"
        }
    },
    "highlight": {
        "fields": {
            "content": {}
        }
    }
}

在上述示例中,highlight部分指定了要高亮显示的字段,这里是content字段。默认情况下,ElasticSearch会使用<em>标签包围高亮的关键词。

自定义高亮标签

我们可以自定义高亮显示的标签。例如,使用<strong>标签来包围高亮关键词:

{
    "query": {
        "match": {
            "content": "科技进展"
        }
    },
    "highlight": {
        "pre_tags": ["<strong>"],
        "post_tags": ["</strong>"],
        "fields": {
            "content": {}
        }
    }
}

在上述示例中,pre_tags指定了高亮关键词前的标签,post_tags指定了高亮关键词后的标签。

片段大小和数量

ElasticSearch允许我们控制高亮片段的大小和数量。例如,我们只希望每个高亮部分显示100个字符,并且最多显示3个片段:

{
    "query": {
        "match": {
            "content": "科技进展"
        }
    },
    "highlight": {
        "fragment_size": 100,
        "num_fragments": 3,
        "fields": {
            "content": {}
        }
    }
}

这样设置后,搜索结果中的高亮部分将以不超过100个字符的片段显示,最多显示3个这样的片段。

ElasticSearch搜索API的模糊搜索

模糊搜索允许我们在搜索关键词不完全匹配的情况下找到相关结果,这在处理拼写错误或相似词汇时非常有用。

基本的模糊查询

假设我们有一个包含城市名称的索引,我们想要搜索与“shangai”相似的城市名称(实际应为“shanghai”)。可以使用模糊查询:

{
    "query": {
        "fuzzy": {
            "city_name": {
                "value": "shangai",
                "fuzziness": "AUTO"
            }
        }
    }
}

在上述示例中,fuzziness设置为“AUTO”,表示ElasticSearch会根据关键词的长度自动确定模糊度。一般来说,较短的关键词会有较高的模糊度。

自定义模糊度

我们也可以自定义模糊度。模糊度表示关键词与匹配项之间允许的编辑距离(例如,插入、删除、替换字符的数量)。例如,设置模糊度为2:

{
    "query": {
        "fuzzy": {
            "city_name": {
                "value": "shangai",
                "fuzziness": 2
            }
        }
    }
}

这样,只要城市名称与“shangai”的编辑距离不超过2,就会被匹配到。

前缀长度和最大扩展

prefix_length参数可以指定关键词的前缀部分必须完全匹配,以减少模糊搜索的范围。max_expansions参数可以限制模糊搜索时尝试的扩展数量,从而提高查询性能。

{
    "query": {
        "fuzzy": {
            "city_name": {
                "value": "shangai",
                "fuzziness": 2,
                "prefix_length": 3,
                "max_expansions": 5
            }
        }
    }
}

在上述示例中,prefix_length为3,表示“shangai”的前3个字符“sha”必须完全匹配。max_expansions为5,表示最多尝试5种模糊扩展。

通过以上对ElasticSearch搜索API高级用法的介绍,我们可以看到它在处理复杂搜索需求时的强大功能。无论是多字段搜索、嵌套查询、聚合分析,还是地理空间查询等,都为我们在实际应用中提供了丰富的手段来获取准确、有用的信息。