elasticsearch 搜索 (二):多字符匹配、SLOP、前缀、正则、通配符、权重boost

1 多字符查询
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "War and Peace" }},
{ "match": { "author": "Leo Tolstoy" }}
]
}
}
}

bool 查询采取 more-matches-is-better 匹配越多越好的方式,所以每条 match 语句的评分结果会被加在一起,从而为每个文档提供最终的分数 _score 。能与两条语句同时匹配的文档比只与一条语句匹配的文档得分要高。

2. 最佳字段
PUT /my_index/_doc/1
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}

PUT /my_index/_doc/2
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
当我们去搜Brown fox
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}

结果发现:文档2 的评分更高,为了理解导致这样的原因,需要回想一下 bool 是如何计算评分的:
它会执行 should 语句中的两个查询。
加和两个查询的评分。
乘以匹配语句的总数。
除以所有语句总数(这里为:2)。
因为文档1俩字段都有brown,所以两个 match 语句都能成功匹配并且有一个评分。文档 2 的 body 字段同时包含 brown 和 fox 这两个词,但 title 字段没有包含任何词。这样, body 查询结果中的高分,加上 title 查询中的 0 分,然后乘以二分之一,就得到比文档 1 更低的整体评分。

这显然不是理想结果:这种情况下就需要用dis_max 分离 最大化查询(Disjunction Max Query) 将任何与任一查询匹配的文档作为结果返回,但只将最佳匹配的评分作为查询的评分结果返回 :
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
dis_max 查询只会简单地使用 单个 最佳匹配语句的评分 _score 作为整体评分

3 tie_breaker 参数
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
],
"tie_breaker": 0.3
}
}
}
这个参数将其他匹配语句的评分也考虑其中
tie_breaker 参数提供了一种 dis_max 和 bool 之间的折中选择,它的评分方式如下:
获得最佳匹配语句的评分 _score 。
将其他匹配语句的评分结果与 tie_breaker 相乘。
对以上评分求和并规范化。
有了 tie_breaker ,会考虑所有匹配语句,但最佳匹配语句依然占最终结果里的很大一部分。
tie_breaker 可以是 0 到 1 之间的浮点数,其中 0 代表使用 dis_max 最佳匹配语句的普通逻辑, 1 表示所有匹配语句同等重要。最佳的精确值需要根据数据与查询调试得出,但是合理值应该与零接近(处于 0.1 - 0.4 之间),这样就不会颠覆 dis_max 最佳匹配性质的根本。

4 短语匹配
match_phrase
match_phrase 查询首先将查询字符串解析成一个词项列表,然后对这些词项进行搜索,但只保留那些包含 全部 搜索词项,且 位置 与搜索词项相同的文档

5 SLOP

让quick fox 匹配到quick brown fox 使用 slop
{
"query": {
"match_phrase": {
"title": {
"query": "quick fox",
"slop": 1
}
}
}
}

简单理解为:slop 参数告诉 match_phrase 查询词条相隔多远时仍然能将文档视为匹配
The quick brown fox jumps over the quick dog 这样可以匹配到,如果query换成 quick jumps 那么 slop 需要填写成2

6 多值字段
{
"names": [ "John Abraham", "Lincoln Smith"]
}

检索这种数据:
{
"query": {
"match_phrase": {
"names": "Abraham Lincoln"
}
}
}

会检索到以上的数据 但是我在7.13.2 版本中 没有搜到,大概率跟版本有关系

解决方案: position_increment_gap 告诉 Elasticsearch 应该为数组中每个新元素增加当前词条 position 的指定值。指定了这个你可能要增加一定的slop 来查询到这个曾经的美国总统了。

7 部分匹配
允许用户指定查找词的一部分并找出所有包含这部分片段的词。
使用场景:
匹配邮编、产品序列号或其他 not_analyzed 未分析值,这些值可以是以某个特定前缀开始,也可以是与某种模式匹配的,甚至可以是与某个正则式相匹配的

输入即搜索(search-as-you-type) ——在用户键入搜索词过程的同时就呈现最可能的结果

匹配如德语或荷兰语这样有长组合词的语言,如: Weltgesundheitsorganisation (世界卫生组织,英文 World Health Organization)

8 prefix 前缀查询
查询是一个词级别的底层的查询,它不会在搜索之前分析查询字符串,它假定传入前缀就正是要查找的前缀。
查询demo:{
"query": {
"prefix": {
"postcode": "W1"
}
}
}

默认状态下, prefix 查询不做相关度评分计算,它只是将所有匹配的文档返回,并为每条结果赋予评分值 1 。它的行为更像是过滤器而不是查询。 prefix 查询和 prefix 过滤器这两者实际的区别就是过滤器是可以被缓存的,而查询不行。

tip:
默认状态下, prefix 查询不做相关度评分计算,它只是将所有匹配的文档返回,并为每条结果赋予评分值 1 。它的行为更像是过滤器而不是查询。 prefix 查询和 prefix 过滤器这两者实际的区别就是过滤器是可以被缓存的,而查询不行

prefix 查询或过滤对于一些特定的匹配是有效的,但使用方式还是应当注意。当字段中词的集合很小时,可以放心使用,但是它的伸缩性并不好,会对我们的集群带来很多压力。可以使用较长的前缀来限制这种影响,减少需要访问的量

9 通配符查询
wildcard 它使用标准的 shell 通配符查询: ? 匹配任意字符, * 匹配 0 或多个字符。它允许指定匹配的正则式。
{
"query": {
"wildcard": {
"make": "W?F*HW"
}
}
}

10 正则表达式
{
"query": {
"regexp": {
"postcode": "W[0-9].+"
}
}
}

wildcard 和 regexp 查询的工作方式与 prefix 查询完全一样,它们也需要扫描倒排索引中的词列表才能找到所有匹配的词,然后依次获取每个词相关的文档 ID ,与 prefix 查询的唯一不同是:它们能支持更为复杂的匹配模式。
这也意味着需要同样注意前缀查询存在性能问题,对有很多唯一词的字段执行这些查询可能会消耗非常多的资源,所以要避免使用左通配这样的模式匹配(如: *foo 或 .*foo 这样的正则式)。
数据在索引时的预处理有助于提高前缀匹配的效率,而通配符和正则表达式查询只能在查询时完成,尽管这些查询有其应用场景,但使用仍需谨慎

prefix 、 wildcard 和 regexp 查询是基于词操作的,如果用它们来查询 analyzed 字段,它们会检查字段里面的每个词,而不是将字段作为整体来处理

11 及时搜索或输入即搜索
{
"match_phrase_prefix" : {
"brand" : "johnnie walker bl"
}
}

{
"match_phrase_prefix" : {
"brand" : {
"query": "walker johnnie bl",
"slop": 10
}
}
}

可以通过设置 max_expansions 参数来限制前缀扩展的影响,一个合理的值是可能是 50 :
{
"match_phrase_prefix" : {
"brand" : {
"query": "johnnie walker bl",
"max_expansions": 50
}
}
}

11 提升权重
设想有个网站供用户发布博客并且可以让他们为自己喜欢的博客点赞,我们希望将更受欢迎的博客放在搜索结果列表中相对较上的位置,同时全文搜索的评分仍然作为相关度的主要排序依据,可以简单的通过存储每个博客的点赞数来实现它

这就要用到function_score ,function_score 查询 是用来控制评分过程的终极武器,它允许为每个与主查询匹配的文档应用一个函数,以达到改变甚至完全替换原始查询评分 _score 的目的。
实际上,也能用过滤器对结果的 子集 应用不同的函数,这样一箭双雕:既能高效评分,又能利用过滤器缓存。
Elasticsearch 预定义了一些函数:
weight
为每个文档应用一个简单而不被规范化的权重提升值:当 weight 为 2 时,最终结果为 2 * _score 。
field_value_factor
使用这个值来修改 _score ,如将 popularity 或 votes (受欢迎或赞)作为考虑因素
random_score
为每个用户都使用一个不同的随机评分对结果排序,但对某一具体用户来说,看到的顺序始终是一致的。

衰减函数 —— linear 、 exp 、 gauss
将浮动值结合到评分 _score 中,例如结合 publish_date 获得最近发布的文档,结合 geo_location 获得更接近某个具体经纬度(lat/lon)地点的文档,结合 price 获得更接近某个特定价格的文档。

script_score
如果需求超出以上范围时,用自定义脚本可以完全控制评分计算,实现所需逻辑
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes"
}
}
}
}

field_value_factor 函数会被应用到每个与主 query 匹配的文档。

12 modifier
一种融入受欢迎度更好方式是用 modifier 平滑 votes 的值。换句话说,我们希望最开始的一些赞更重要,但是其重要性会随着数字的增加而降低。 0 个赞与 1 个赞的区别应该比 10 个赞与 11 个赞的区别大很多
对于上述情况,典型的 modifier 应用是使用 log1p 参数值,公式如下:
new_score = old_score * log(1 + number_of_votes)
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p"
}
}
}
}
修饰语 modifier 的值可以为: none (默认状态)、 log 、 log1p 、 log2p 、 ln 、 ln1p 、 ln2p 、 square 、 sqrt 以及 reciprocal 。想要了解更多信息请参照: field_value_factor 文档.

factor
可以通过将 votes 字段与 factor 的积来调节受欢迎程度效果的高低:
"field_value_factor": {
"field": "votes",
"modifier": "log1p",
"factor": 2
}

添加了 factor 会使公式变成这样:
new_score = old_score * log(1 + factor * number_of_votes)
factor 值大于 1 会提升效果, factor 值小于 1 会降低效果

boost_mode
或许将全文评分与 field_value_factor 函数值乘积的效果仍然可能太大,我们可以通过参数 boost_mode 来控制函数与查询评分 _score 合并后的结果,参数接受的值为:
multiply
评分 _score 与函数值的积(默认)
sum
评分 _score 与函数值的和
min
评分 _score 与函数值间的较小值
max
评分 _score 与函数值间的较大值
replace
函数值替代评分 _score
与使用乘积的方式相比,使用评分 _score 与函数值求和的方式可以弱化最终效果,特别是使用一个较小 factor 因子时:
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p",
"factor": 0.1
},
"boost_mode": "sum"
}
}
}

new_score = old_score + log(1 + 0.1 * number_of_votes)

防止无序扩张,设置一个max_boost 限制一个函数的最大效果
"boost_mode": "sum",
"max_boost": 1.5
无论 field_value_factor 函数的结果如何,最终结果都不会大于 1.5 ,
max_boost 只对函数的结果进行限制,不会对最终评分 _score 产生直接影响。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注