1.多字符串查询:
如果我们知道 War and Peace 是标题,Leo Tolstoy 是作者,很容易就能把两个条件用 match 语句表示,并将它们用 bool 查询 组合起来:
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "War and Peace" }},
{ "match": { "author": "Leo Tolstoy" }}
]
}
}
}
我们可以在上面的示例中添加一条语句来指定译者版本的偏好:
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "War and Peace" }},
{ "match": { "author": "Leo Tolstoy" }},
{ "bool": {
"should": [
{ "match": { "translator": "Constance Garnett" }},
{ "match": { "translator": "Louise Maude" }}
]
}}
]
}
}
}
为什么将译者条件语句放入另一个独立的 bool 查询中呢?所有的四个 match 查询都是 should 语句,所以为什么不将 translator 语句与其他如 title 、 author 这样的语句放在同一层呢?
答案在于评分的计算方式。 bool 查询运行每个 match 查询,再把评分加在一起,然后将结果与所有匹配的语句数量相乘,最后除以所有的语句数量。处于同一层的每条语句具有相同的权重。
在前面这个例子中,包含 translator 语句的 bool 查询,只占总评分的三分之一。
如果将 translator 语句与 title 和 author 两条语句放入同一层,那么 title 和 author 语句只贡献四分之一评分。
2.语句的优先级
前例中每条语句贡献三分之一评分的这种方式可能并不是我们想要的,我们可能对 title 和 author 两条语句更感兴趣,这样就需要调整查询,使 title 和 author 语句相对来说更重要。
在武器库中,最容易使用的就是 boost 参数。为了提升 title 和 author 字段的权重,为它们分配的 boost 值大于 1 :
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": {
"title": {
"query": "War and Peace",
"boost": 2
}}},
{ "match": {
"author": {
"query": "Leo Tolstoy",
"boost": 2
}}},
{ "bool": {
"should": [
{ "match": { "translator": "Constance Garnett" }},
{ "match": { "translator": "Louise Maude" }}
]
}}
]
}
}
}
3. 最佳字段查询:dis_max 即分离 最大化查询(Disjunction Max Query):分离(Disjunction)的意思是 或(or) ,这与可以把结合(conjunction)理解成 与(and) 相对应。分离最大化查询(Disjunction Max Query)
指的是: 将任何与任一查询匹配的文档作为结果返回,但只将最佳匹配的评分作为查询的评分结果返回 :
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
4.可以通过指定 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 可以是 0 到 1 之间的浮点数,其中 0 代表使用 dis_max 最佳匹配语句的普通逻辑, 1 表示所有匹配语句同等重要。
最佳的精确值需要根据数据与查询调试得出,但是合理值应该与零接近(处于 0.1 - 0.4 之间),这样就不会颠覆 dis_max 最佳匹配性质的根本。
4.multi_match 查询为能在多个字段上反复执行相同查询提供了一种便捷方式
默认情况下,查询的类型是 best_fields ,这表示它会为每个字段生成一个 match 查询,然后将它们组合到 dis_max 查询的内部,如下:{
"dis_max": {
"queries": [
{
"match": {
"title": {
"query": "Quick brown fox",
"minimum_should_match": "30%"
}
}
},
{
"match": {
"body": {
"query": "Quick brown fox",
"minimum_should_match": "30%"
}
}
},
],
"tie_breaker": 0.3
}
}
上面这个查询用 multi_match 重写成更简洁的形式:
{
"multi_match": {
"query": "Quick brown fox",
"type": "best_fields",
"fields": [ "title", "body" ],
"tie_breaker": 0.3,
"minimum_should_match": "30%"
}
}
5.查询字段名称的模糊匹配:
{
"multi_match": {
"query": "Quick brown fox",
"fields": "*_title"
}
}
可以使用 ^ 字符语法为单个字段提升权重,在字段名称的末尾添加 ^boost ,其中 boost 是一个浮点数:
{
"multi_match": {
"query": "Quick brown fox",
"fields": [ "*_title", "chapter_title^2" ]
}
}
注:chapter_title 这个字段的 boost 值为 2 ,而其他两个字段 book_title 和 section_title 字段的默认 boost 值为 1 。
6.多字段映射:首先要做的事情就是对我们的字段索引两次:一次使用词干模式以及一次非词干模式。为了做到这点,采用 multifields 来实现,已经在 multifields 有所介绍:
PUT /my_index
{
"settings": { "number_of_shards": 1 },
"mappings": {
"my_type": {
"properties": {
"title": {
"type": "string",
"analyzer": "english",
"fields": {
"std": {
"type": "string",
"analyzer": "standard"
}
}
}
}
}
}
}
7.GET /my_index/_search
{
"query": {
"multi_match": {
"query": "jumping rabbits",
"type": "most_fields", //我们希望将所有匹配字段的评分合并起来,所以使用 most_fields 类型。这让 multi_match 查询用 bool 查询将两个字段语句包在里面,而不是使用 dis_max 查询。
"fields": [ "title", "title.std" ]
}
}
}
8.每个字段对于最终评分的贡献可以通过自定义值 boost 来控制。比如,使 title 字段更为重要,这样同时也降低了其他信号字段的作用:
GET /my_index/_search
{
"query": {
"multi_match": {
"query": "jumping rabbits",
"type": "most_fields",
"fields": [ "title^10", "title.std" ]
}
}
}
注:title 字段的 boost 的值为 10 使它比 title.std 更重要。
9.自定义_all字段:
_all 字段的索引方式是将所有其他字段的值作为一个大字符串索引的。然而这么做并不十分灵活,为了灵活我们可以给人名添加一个自定义 _all 字段,再为地址添加另一个 _all 字段。
Elasticsearch 在字段映射中为我们提供 copy_to 参数来实现这个功能:
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"
}
}
}
}
}
10.跨字段查询:
GET /_validate/query?explain
{
"query": {
"multi_match": {
"query": "peter smith",
"type": "cross_fields",
"operator": "and",
"fields": [ "first_name", "last_name" ]
}
}
}
11.提高字段权重:采用 cross_fields 查询与 自定义 _all 字段 相比,其中一个优势就是它可以在搜索时为单个字段提升权重。
这对像 first_name 和 last_name 具有相同值的字段并不是必须的,但如果要用 title 和 description 字段搜索图书,可能希望为 title 分配更多的权重,这同样可以使用前面介绍过的 ^ 符号语法来实现:
GET /books/_search
{
"query": {
"multi_match": {
"query": "peter smith",
"type": "cross_fields",
"fields": [ "title^2", "description" ]
}
}
}
12.短语匹配查询:就像 match 查询对于标准全文检索是一种最常用的查询一样,当你想找到彼此邻近搜索词的查询方法时,就会想到 match_phrase 查询。
GET /my_index/my_type/_search
{
"query": {
"match_phrase": {
"title": "quick brown fox"
}
}
}
类似 match 查询, match_phrase 查询首先将查询字符串解析成一个词项列表,然后对这些词项进行搜索,但只保留那些包含 全部 搜索词项,且 位置 与搜索词项相同的文档。
match_phrase 查询同样可写成一种类型为 phrase 的 match 查询:
"match": {
"title": {
"query": "quick brown fox",
"type": "phrase"
}
}
13.精确短语匹配 或许是过于严格了。也许我们想要包含 “quick brown fox” 的文档也能够匹配 “quick fox,” , 尽管情形不完全相同。
我们能够通过使用 slop 参数将灵活度引入短语匹配中:
GET /my_index/my_type/_search
{
"query": {
"match_phrase": {
"title": {
"query": "quick fox",
"slop": 1
}
}
}
}
slop 参数告诉 match_phrase 查询词条相隔多远时仍然能将文档视为匹配 。 相隔多远的意思是为了让查询和文档匹配你需要移动词条多少次?
14.多值字段:
PUT /my_index/groups/1
{
"names": [ "John Abraham", "Lincoln Smith"]
}
然后运行一个对 Abraham Lincoln 的短语查询:
GET /my_index/groups/_search
{
"query": {
"match_phrase": {
"names": "Abraham Lincoln"
}
}
}
令人惊讶的是, 即使 Abraham 和 Lincoln 在 names 数组里属于两个不同的人名, 我们的文档也匹配了查询。 这一切的原因在Elasticsearch数组的索引方式。
Elasticsearch对以上数组分析生成了与分析单个字符串 John Abraham Lincoln Smith 一样几乎完全相同的语汇单元。 我们的查询示例寻找相邻的 lincoln 和 abraham , 而且这两个词条确实存在,并且它们俩正好相邻, 所以这个查询匹配了。
幸运的是, 在这样的情况下有一种叫做 position_increment_gap 的简单的解决方案, 它在字段映射中配置。
DELETE /my_index/groups/
PUT /my_index/_mapping/groups
{
"properties": {
"names": {
"type": "string",
"position_increment_gap": 100
}
}
}
注:
首先删除映射 groups 以及这个类型内的所有文档。
然后创建一个有正确值的新的映射 groups 。
position_increment_gap 设置告诉 Elasticsearch 应该为数组中每个新元素增加当前词条 position 的指定值。 所以现在当我们再索引 names 数组时,会产生如下的结果:
Position 1: john
Position 2: abraham
Position 103: lincoln
Position 104: smith
现在我们的短语查询可能无法匹配该文档因为 abraham 和 lincoln 之间的距离为 100 。 为了匹配这个文档你必须添加值为 100 的 slop 。
15.越近越好:鉴于一个短语查询仅仅排除了不包含确切查询短语的文档, 而 邻近查询 — 一个 slop 大于 0— 的短语查询将查询词条的邻近度考虑到最终相关度 _score 中。
通过设置一个像 50 或者 100 这样的高 slop 值, 你能够排除单词距离太远的文档, 但是也给予了那些单词临近的的文档更高的分数。
POST /my_index/my_type/_search
{
"query": {
"match_phrase": {
"title": {
"query": "quick dog",
"slop": 50
}
}
}
}
16.使用临近度提高相关度
们可以将一个简单的 match 查询作为一个 must 子句。 这个查询将决定哪些文档需要被包含到结果集中。 我们可以用 minimum_should_match 参数去除长尾。
然后我们可以以 should 子句的形式添加更多特定查询。 每一个匹配成功的都会增加匹配文档的相关度。
GET /my_index/my_type/_search
{
"query": {
"bool": {
"must": {
"match": {
"title": {
"query": "quick brown fox",
"minimum_should_match": "30%"
}
}
},
"should": {
"match_phrase": {
"title": {
"query": "quick brown fox",
"slop": 50
}
}
}
}
}
}
17.结果集重新评分:
一个简单的 match 查询已经通过排序把包含所有含有搜索词条的文档放在结果列表的前面了。事实上,我们只想对这些 顶部文档 重新排序,来给同时匹配了短语查询的文档一个额外的相关度升级。
search API 通过 重新评分 明确支持该功能。重新评分阶段支持一个代价更高的评分算法—比如 phrase 查询—只是为了从每个分片中获得前 K 个结果。 然后会根据它们的最新评分 重新排序。
该请求如下所示:
GET /my_index/my_type/_search
{
"query": {
"match": {
"title": {
"query": "quick brown fox",
"minimum_should_match": "30%"
}
}
},
"rescore": {
"window_size": 50,
"query": {
"rescore_query": {
"match_phrase": {
"title": {
"query": "quick brown fox",
"slop": 50
}
}
}
}
}
}
注:
match 查询决定哪些文档将包含在最终结果集中,并通过 TF/IDF 排序。
window_size 是每一分片进行重新评分的顶部文档数量。
目前唯一支持的重新打分算法就是另一个查询,但是以后会有计划增加更多的算法。
18.Shingles 需要在索引时作为分析过程的一部分被创建。我们可以将 unigrams 和 bigrams 都索引到单个字段中, 但将它们分开保存在能被独立查询的字段会更清晰。
unigrams 字段将构成我们搜索的基础部分,而 bigrams 字段用来提高相关度。
首先,我们需要在创建分析器时使用 shingle 语汇单元过滤器:
DELETE /my_index
PUT /my_index
{
"settings": {
"number_of_shards": 1,
"analysis": {
"filter": {
"my_shingle_filter": {
"type": "shingle",
"min_shingle_size": 2,
"max_shingle_size": 2,
"output_unigrams": false
}
},
"analyzer": {
"my_shingle_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"my_shingle_filter"
]
}
}
}
}
}
注:
默认最小/最大的 shingle 大小是 2 ,所以实际上不需要设置。
shingle 语汇单元过滤器默认输出 unigrams ,但是我们想让 unigrams 和 bigrams 分开。
my_shingle_analyzer 使用我们常规的 my_shingles_filter 语汇单元过滤器。
19.部分匹配:
为了找到所有以 W1 开始的邮编,可以使用简单的 prefix 查询:
GET /my_index/address/_search
{
"query": {
"prefix": {
"postcode": "W1"
}
}
}
20.与 prefix 前缀查询的特性类似, wildcard 通配符查询也是一种底层基于词的查询,与前缀查询不同的是它允许指定匹配的正则式。
它使用标准的 shell 通配符查询: ? 匹配任意字符, * 匹配 0 或多个字符。
这个查询会匹配包含 W1F 7HW 和 W2F 8HW 的文档:
GET /my_index/address/_search
{
"query": {
"wildcard": {
"postcode": "W?F*HW"
}
}
}
设想如果现在只想匹配 W 区域的所有邮编,前缀匹配也会包括以 WC 开头的所有邮编,与通配符匹配碰到的问题类似,如果想匹配只以 W 开始并跟随一个数字的所有邮编,
regexp 正则式查询允许写出这样更复杂的模式:
GET /my_index/address/_search
{
"query": {
"regexp": {
"postcode": "W[0-9].+"
}
}
}
21.对于查询时的输入即搜索,可以使用 match_phrase 的一种特殊形式, match_phrase_prefix 查询:
{
"match_phrase_prefix" : {
"brand" : "johnnie walker bl"
}
}
与 match_phrase 一样,它也可以接受 slop 参数(参照 slop )让相对词序位置不那么严格:
{
"match_phrase_prefix" : {
"brand" : {
"query": "walker johnnie bl",
"slop": 10
}
}
}
在之前的 前缀查询 中,我们警告过使用前缀的风险,即 prefix 查询存在严重的资源消耗问题,短语查询的这种方式也同样如此。前缀 a 可能会匹配成千上万的词,这不仅会消耗很多系统资源,
而且结果的用处也不大。可以通过设置 max_expansions 参数来限制前缀扩展的影响,一个合理的值是可能是 50 :
{
"match_phrase_prefix" : {
"brand" : {
"query": "johnnie walker bl",
"max_expansions": 50
}
}
}
我们需要保证倒排索引表中包含边界 n-grams 的每个词,但是我们只想匹配用户输入的完整词组( brown 和 fo ),可以通过在索引时使用 autocomplete 分析器,
并在搜索时使用 standard 标准分析器来实现这种想法,只要改变查询使用的搜索分析器 analyzer 参数即可:
GET /my_index/my_type/_search
{
"query": {
"match": {
"name": {
"query": "brown fo",
"analyzer": "standard"
}
}
}
}
换种方式,我们可以在映射中,为 name 字段分别指定 index_analyzer 和 search_analyzer 。因为我们只想改变 search_analyzer ,这里只要更新现有的映射而不用对数据重新创建索引:
PUT /my_index/my_type/_mapping
{
"my_type": {
"properties": {
"name": {
"type": "string",
"index_analyzer": "autocomplete",
"search_analyzer": "standard"
}
}
}
}
注:在索引时,使用 autocomplete 分析器生成边界 n-grams 的每个词。
在搜索时,使用 standard 分析器只搜索用户输入的词。
22.下面示例使用 keyword 分词器将邮编转换成 token 流,这样就能使用边界 n-gram token 过滤器:
{
"analysis": {
"filter": {
"postcode_filter": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 8
}
},
"analyzer": {
"postcode_index": {
"tokenizer": "keyword",
"filter": [ "postcode_filter" ]
},
"postcode_search": {
"tokenizer": "keyword"
}
}
}
}
注:
postcode_index 分析器使用 postcode_filter 将邮编转换成边界 n-gram 形式。
postcode_search 分析器可以将搜索词看成 not_analyzed 未分析的。
22.如果不在意词在某个字段中出现的频次,而只在意是否出现过,则可以在字段映射中禁用词频统计:
PUT /my_index
{
"mappings": {
"doc": {
"properties": {
"text": {
"type": "string",
"index_options": "docs"
}
}
}
}
}
注:将参数 index_options 设置为 docs 可以禁用词频统计及词频位置,这个映射的字段不会计算词的出现次数,
对于短语或近似查询也不可用。要求精确查询的 not_analyzed 字符串字段会默认使用该设置。
23.字段长度的归一值对全文搜索非常重要,许多其他字段不需要有归一值。无论文档是否包括这个字段,索引中每个文档的每个 string 字段都大约占用 1 个 byte 的空间。
对于 not_analyzed 字符串字段的归一值默认是禁用的,而对于 analyzed 字段也可以通过修改字段映射禁用归一值:
PUT /my_index
{
"mappings": {
"doc": {
"properties": {
"text": {
"type": "string",
"norms": { "enabled": false }
}
}
}
}
}
24.查询时权重提升:
GET /_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": {
"query": "quick brown fox",
"boost": 2
}
}
},
{
"match": {
"content": "quick brown fox"
}
}
]
}
}
}
25.提升索引权重:
当在多个索引中搜索时,可以使用参数 indices_boost 来提升整个索引的权重,在下面例子中,当要为最近索引的文档分配更高权重时,可以这么做:
GET /docs_2014_*/_search
{
"indices_boost": {
"docs_2014_10": 3,
"docs_2014_09": 2
},
"query": {
"match": {
"text": "quick brown fox"
}
}
}
注:其中,索引 docs_2014_10 中的所有文件的权重是 3 ,索引 docs_2014_09 中是 2 ,其他所有匹配的索引权重为默认值 1 。
26.not Quite not :
在互联网上搜索 “Apple”,返回的结果很可能是一个公司、水果和各种食谱。我们可以在 bool 查询中用 must_not 语句来排除像 pie 、 tart 、 crumble 和 tree 这样的词,
从而将查询结果的范围缩小至只返回与 “Apple” (苹果)公司相关的结果:
GET /_search
{
"query": {
"bool": {
"must": {
"match": {
"text": "apple"
}
},
"must_not": {
"match": {
"text": "pie tart fruit crumble tree"
}
}
}
}
}
但谁又敢保证在排除 tree 或 crumble 这种词后,不会错失一个与苹果公司特别相关的文档呢?有时, must_not 条件会过于严格。
权重提升查询:
boosting 查询 恰恰能解决这个问题。它仍然允许我们将关于水果或甜点的结果包括到结果中,但是使它们降级——即降低它们原来可能应有的排名:
GET /_search
{
"query": {
"boosting": {
"positive": {
"match": {
"text": "apple"
}
},
"negative": {
"match": {
"text": "pie tart fruit crumble tree"
}
},
"negative_boost": 0.5
}
}
}
它接受 positive 和 negative 查询。只有那些匹配 positive 查询的文档罗列出来,对于那些同时还匹配 negative 查询的文档将通过文档的原始 _score 与 negative_boost 相乘的方式降级后的结果。
为了达到效果, negative_boost 的值必须小于 1.0 。在这个示例中,所有包含负向词的文档评分 _score 都会减半。
27:忽略TF/IDF:在 constant_score 查询中,它可以包含查询或过滤,为任意一个匹配的文档指定评分 1 ,忽略 TF/IDF 信息:
GET /_search
{
"query": {
"bool": {
"should": [
{ "constant_score": {
"query": { "match": { "description": "wifi" }}
}},
{ "constant_score": {
"query": { "match": { "description": "garden" }}
}},
{ "constant_score": {
"query": { "match": { "description": "pool" }}
}}
]
}
}
}
或许不是所有的设施都同等重要——对某些用户来说有些设施更有价值。如果最重要的设施是游泳池,那我们可以为更重要的设施增加权重:
GET /_search
{
"query": {
"bool": {
"should": [
{ "constant_score": {
"query": { "match": { "description": "wifi" }}
}},
{ "constant_score": {
"query": { "match": { "description": "garden" }}
}},
{ "constant_score": {
"boost": 2
"query": { "match": { "description": "pool" }}
}}
]
}
}
}
注:pool 语句的权重提升值为 2 ,而其他的语句为 1 。
默认情况下,一个 not_analyzed 字段会禁用 字段长度归一值(field-length norms) 的功能,并将 index_options 设为 docs 选项,禁用 词频 ,但还是存在问题:每个词的 倒排文档频率 仍然会被考虑。
可以采用与之前相同的方法 constant_score 查询来解决这个问题:
GET /_search
{
"query": {
"bool": {
"should": [
{ "constant_score": {
"query": { "match": { "features": "wifi" }}
}},
{ "constant_score": {
"query": { "match": { "features": "garden" }}
}},
{ "constant_score": {
"boost": 2
"query": { "match": { "features": "pool" }}
}}
]
}
}
}
这里的问题是:过滤器无法计算评分。这样就需要寻求一种方式将过滤器和查询间的差异抹平。 function_score 查询不仅正好可以扮演这个角色,而且有更强大的功能。
28.function_score 查询 是用来控制评分过程的终极武器,它允许为每个与主查询匹配的文档应用一个函数,以达到改变甚至完全替换原始查询评分 _score 的目的。
设想有个网站供用户发布博客并且可以让他们为自己喜欢的博客点赞,我们希望将更受欢迎的博客放在搜索结果列表中相对较上的位置,同时全文搜索的评分仍然作为相关度的主要排序依据,
可以简单的通过存储每个博客的点赞数来实现它:
PUT /blogposts/post/1
{
"title": "About popularity",
"content": "In this post we will talk about...",
"votes": 6
}
在搜索时,可以将 function_score 查询与 field_value_factor 结合使用,即将点赞数与全文相关度评分结合:
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": { //field_value_factor 函数会被应用到每个与主 query 匹配的文档。
"field": "votes" //每个文档的 votes 字段都 必须 有值供 function_score 计算。如果 没有 文档的 votes 字段有值,那么就 必须 使用 missing 属性 提供的默认值来进行评分计算。
}
}
}
}
29.一种融入受欢迎度更好方式是用 modifier 平滑 votes 的值。换句话说,我们希望最开始的一些赞更重要,但是其重要性会随着数字的增加而降低。
0 个赞与 1 个赞的区别应该比 10 个赞与 11 个赞的区别大很多。
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p"
}
}
}
}
注:modifier 为 log1p 。
修饰语 modifier 的值可以为: none (默认状态)、 log 、 log1p 、 log2p 、 ln 、 ln1p 、 ln2p 、 square 、 sqrt 以及 reciprocal 。
30.可以通过将 votes 字段与 factor 的积来调节受欢迎程度效果的高低:
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p",
"factor": 2
}
}
}
}
31.或许将全文评分与 field_value_factor 函数值乘积的效果仍然可能太大,我们可以通过参数 boost_mode 来控制函数与查询评分 _score 合并后的结果,参数接受的值为:
multiply
评分 _score 与函数值的积(默认)
sum
评分 _score 与函数值的和
min
评分 _score 与函数值间的较小值
max
评分 _score 与函数值间的较大值
replace
函数值替代评分 _score
与使用乘积的方式相比,使用评分 _score 与函数值求和的方式可以弱化最终效果,特别是使用一个较小 factor 因子时:
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p",
"factor": 0.1
},
"boost_mode": "sum"
}
}
}
32.最后,可以使用 max_boost 参数限制一个函数的最大效果:
GET /blogposts/post/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": {
"field": "votes",
"modifier": "log1p",
"factor": 0.1
},
"boost_mode": "sum",
"max_boost": 1.5
}
}
}
33.我们希望根据每个度假屋的特性数量来评分,当时我们希望能用缓存的过滤器来影响评分,现在 function_score 查询正好可以完成这件事情。
我们会使用 weight 函数,它与 boost 参数类似可以用于任何查询。有一点区别是 weight 没有被 Luence 归一化成难以理解的浮点数,而是直接被应用。
GET /_search
{
"query": {
"function_score": {
"filter": { //function_score 查询有个 filter 过滤器而不是 query 查询。
"term": { "city": "Barcelona" }
},
"functions": [ //functions 关键字存储着一个将被应用的函数列表
{
"filter": { "term": { "features": "wifi" }}, //函数会被应用于和 filter 过滤器(可选的)匹配的文档
"weight": 1
},
{
"filter": { "term": { "features": "garden" }},
"weight": 1
},
{
"filter": { "term": { "features": "pool" }},
"weight": 2 //pool 比其他特性更重要,所以它有更高 weight 。
}
],
"score_mode": "sum", //score_mode 指定各个函数的值进行组合运算的方式
}
}
}
34:随机评分:作为网站的所有者,总会希望让广告有更高的展现率。在当前查询下,有相同评分 _score 的文档会每次都以相同次序出现,
为了提高展现率,在此引入一些随机性可能会是个好主意,这能保证有相同评分的文档都能有均等相似的展现机率。
我们想让每个用户看到不同的随机次序,但也同时希望如果是同一用户翻页浏览时,结果的相对次序能始终保持一致。这种行为被称为 一致随机(consistently random) 。
random_score 函数会输出一个 0 到 1 之间的数,当种子 seed 值相同时,生成的随机结果是一致的,例如,将用户的会话 ID 作为 seed :
GET /_search
{
"query": {
"function_score": {
"filter": {
"term": { "city": "Barcelona" }
},
"functions": [
{
"filter": { "term": { "features": "wifi" }},
"weight": 1
},
{
"filter": { "term": { "features": "garden" }},
"weight": 1
},
{
"filter": { "term": { "features": "pool" }},
"weight": 2
},
{
"random_score": {
"seed": "the users session id"
}
}
],
"score_mode": "sum"
}
}
}
注:random_score 语句没有任何过滤器 filter ,所以会被应用到所有文档。
将用户的会话 ID 作为种子 seed ,让该用户的随机始终保持一致,相同的种子 seed 会产生相同的随机结果。
35:function_score 查询会提供一组 衰减函数(decay functions) ,让我们有能力在两个滑动标准,如地点和价格,之间权衡。
有三种衰减函数—— linear 、 exp 和 gauss (线性、指数和高斯函数),它们可以操作数值、时间以及经纬度地理坐标点这样的字段。所有三个函数都能接受以下参数:
origin
中心点 或字段可能的最佳值,落在原点 origin 上的文档评分 _score 为满分 1.0 。
scale
衰减率,即一个文档从原点 origin 下落时,评分 _score 改变的速度。(例如,每 £10 欧元或每 100 米)。
decay
从原点 origin 衰减到 scale 所得的评分 _score ,默认值为 0.5 。
offset
以原点 origin 为中心点,为其设置一个非零的偏移量 offset 覆盖一个范围,而不只是单个原点。在范围 -offset <= origin <= +offset 内的所有评分 _score 都是 1.0 。
例如:用户希望租一个离伦敦市中心近( { "lat": 51.50, "lon": 0.12} )且每晚不超过 £100 英镑的度假屋,而且与距离相比,我们的用户对价格更为敏感,这样查询可以写成:
GET /_search
{
"query": {
"function_score": {
"functions": [
{
"gauss": {
"location": {
"origin": { "lat": 51.5, "lon": 0.12 },
"offset": "2km",
"scale": "3km"
}
}
},
{
"gauss": {
"price": {
"origin": "50",
"offset": "50",
"scale": "20"
}
},
"weight": 2
}
]
}
}
}
注:location 字段以地理坐标点 geo_point 映射。
price 字段是数值。
参见 理解价格语句 ,理解 origin 为什么是 50 而不是 100 。
price 语句是 location 语句权重的两倍。
location 语句可以简单理解为:
以伦敦市中作为原点 origin 。
所有距原点 origin 2km 范围内的位置的评分是 1.0 。
距中心 5km ( offset + scale )的位置的评分是 0.5 。
36.如果所有 function_score 内置的函数都无法满足应用场景,可以使用 script_score 函数自行实现逻辑。
举个例子,想将利润空间作为因子加入到相关度评分计算,在业务中,利润空间和以下三点相关:
price 度假屋每晚的价格。
会员用户的级别——某些等级的用户可以在每晚房价高于某个 threshold 阀值价格的时候享受折扣 discount 。
用户享受折扣后,经过议价的每晚房价的利润 margin 。
计算每个度假屋利润的算法如下:
GET /_search
{
"function_score": {
"functions": [
{ ...location clause... },
{ ...price clause... },
{
"script_score": {
"params": {
"threshold": 80,
"discount": 0.1,
"target": 10
},
"script": "price = doc['price'].value; margin = doc['margin'].value;
if (price < threshold) { return price * margin / target };
return price * (1 - discount) * margin / target;"
}
}
]
}
}
37.更改相似度:相似度算法可以按字段指定,只需在映射中为不同字段选定即可:
PUT /my_index
{
"mappings": {
"doc": {
"properties": {
"title": {
"type": "string",
"similarity": "BM25"
},
"body": {
"type": "string",
"similarity": "default"
}
}
}
}
注:
title 字段使用 BM25 相似度算法。
body 字段用默认相似度算法(参见 实用评分函数)。
38.配置相似度算法和配置分析器很相似,自定义相似度算法可以在创建索引时指定,例如:
PUT /my_index
{
"settings": {
"similarity": {
"my_bm25": {
"type": "BM25",
"b": 0
}
}
},
"mappings": {
"doc": {
"properties": {
"title": {
"type": "string",
"similarity": "my_bm25"
},
"body": {
"type": "string",
"similarity": "BM25"
}
}
}
}
}
注:创建一个基于内置 BM25 ,名为 my_bm25 的自定义相似度算法。
禁用字段长度规范化(field-length normalization)。参见 调试 BM25 。
title 字段使用自定义相似度算法 my_bm25 。
字段 body 使用内置相似度算法 BM25 。