Elasticsearch查询类型

news2024/10/17 18:06:09

1. 前言

搜索是Elasticsearch最核心的功能之一,它能够在海量数据中精准、快速地召回我们期望的文档。Elasticsearch支持各种复杂的条件搜索,查询类型之多,往往让新手一头雾水。甚至同一个搜索需求,可以用不同的查询类型来实现,但是效率却天差地别,理解Elasticsearch提供的各种查询类型,可以帮助我们更好的搜索我们的数据。

2. 精确查询

Elasticsearch提供的 term-level queries 术语级查询会精确匹配结构化的字段值来查询文档,结构化字段类型包括:keyword、整型、浮点型、IP、日期等。与全文检索不同的是,精确查询不会分析查询的词,它要求查询词与索引中的文档字段值必须精确匹配。同理,text类型做精确查询是没有意义的,因为Elasticsearch默认不存储text的原始值,而是分词后的词项列表。

2.1 exists

exists 查询用来召回存在给定字段的文档,字段值只要不是null或[]即可。

如下示例,索引的2号文档没有价格,通过exists查询有价格文档,只有1号文档被召回

POST items/_bulk
{"create":{"_id":"1"}}
{"title":"苹果","price":5}
{"create":{"_id":"2"}}
{"title":"香蕉"}

GET items/_search
{
  "query": {
    "exists": {
      "field": "price"
    }
  }
}

// 召回文档
[
  {
    "_index": "items",
    "_id": "1",
    "_score": 1,
    "_source": {
      "title": "苹果",
      "price": 5
    }
  }
]

2.2 fuzzy

fuzzy 是一种强大的搜索类型,它支持编辑距离的模糊搜索,什么意思呢?就是允许用户在单词拼写错误、或者单词拼写正确,但是上下文语义有错误时,也可以召回所需的文档,大大提高了搜索系统的容错能力。

编辑距离是指从一个单词转换到另一个单词,中间需要编辑单字符的次数,转换形式包括:替换、交换、插入和删除。下面分别举例:

  • 替换:box → fox
  • 删除:black → lack
  • 插入:sic → sick
  • 交换:act → cat

如下示例,索引一篇文档,我们想搜”game“,但是单词拼写错误成”gmai“,同时把fuzziness设为2,因为 gmai 转换到 game 需要经过2次转换,最终文档可以成功召回

POST fuzzy-index/_doc
{
  "title": "A cat and mouse game"
}

GET fuzzy-index/_search
{
  "query": {
    "fuzzy": {
      "title": {
        "value": "gmai", // 搜索词
        "fuzziness": 2 // 编辑距离:0~2
      }
    }
  }
}

// 召回文档
[
  {
    "_index": "fuzzy-index",
    "_id": "2Vi36o4BXAgLe9UUtzU6",
    "_score": 0.14384104,
    "_source": {
      "title": "A cat and mouse game"
    }
  }
]

2.3 ids

ids 查询可以根据文档ID批量召回文档,使用非常简单,如下示例:

POST ids-search-index/_bulk
{"create":{"_id":"1"}}
{"title":"doc title1"}
{"create":{"_id":"2"}}
{"title":"doc title2"}

GET ids-search-index/_search
{
  "query": {
    "ids": {
      "values": ["1","2"]
    }
  }
}

// 召回文档
[
  {
    "_index": "ids-search-index",
    "_id": "1",
    "_score": 1,
    "_source": {
      "title": "doc title1"
    }
  },
  {
    "_index": "ids-search-index",
    "_id": "2",
    "_score": 1,
    "_source": {
      "title": "doc title2"
    }
  }
]

2.4 prefix

prefix 是前缀匹配查询,可以根据特定的前缀来查询文档,比如根据姓氏来搜索姓名。

POST prefix-search-index/_bulk
{"create":{"_id":"1"}}
{"name":"张三"}
{"create":{"_id":"2"}}
{"name":"李四"}

GET prefix-search-index/_search
{
  "query": {
    "prefix": {
      "name": {
        "value": "张"
      }
    }
  }
}

// 召回文档
[
  {
    "_index": "prefix-search-index",
    "_id": "1",
    "_score": 1,
    "_source": {
      "name": "张三"
    }
  }
]

2.5 range

range 是范围查询,用来召回所提供范围内的文档。如下示例,召回年龄在18到20的的文档

POST range-search-index/_bulk
{"create":{"_id":"1"}}
{"name":"张三","age":18}
{"create":{"_id":"2"}}
{"name":"李四","age":22}

GET range-search-index/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 18,
        "lte": 20
      }
    }
  }
}

// 召回文档
[
  {
    "_index": "range-search-index",
    "_id": "1",
    "_score": 1,
    "_source": {
      "name": "张三",
      "age": 18
    }
  }
]

2.6 regexp

regexp 是基于正则匹配的查询,正则表达式是一种使用占位符匹配数据模式的方法,功能强大但是性能很差,数据量较大时要避免使用,以防Elasticsearch节点崩溃。

POST regexp-search-index/_bulk
{"create":{"_id":"1"}}
{"title":"Elasticsearch"}
{"create":{"_id":"2"}}
{"title":"Apache Solr"}

GET regexp-search-index/_search
{
  "query": {
    "regexp": {
      "title": ".*search"
    }
  }
}

// 召回文档
[
  {
    "_index": "regexp-search-index",
    "_id": "1",
    "_score": 1,
    "_source": {
      "title": "Elasticsearch"
    }
  }
]

2.7 term

term 是单字段精准术语匹配查询,可以用 term 精准匹配姓名、价格、IP、日期等。如下示例:

POST term-search-index/_bulk
{"create":{"_id":"1"}}
{"name":"Jackson"}
{"create":{"_id":"2"}}
{"name":"Lisa"}

GET term-search-index/_search
{
  "query": {
    "term": {
      "name":{
        "value": "Jackson"
      }
    }
  }
}

// 召回文档
[
  {
    "_index": "term-search-index",
    "_id": "1",
    "_score": 0.6931471,
    "_source": {
      "name": "Jackson"
    }
  }
]

再次提醒,不要对text字段做term查询,这通常没有意义,结果可能会出乎你的意料。如下示例,对text类型的title字段做term查询,即使字段值一模一样,也无法召回文档,因为title存储时已经被分词了。

PUT term-search-index
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text"
      }
    }
  }
}

POST term-search-index/_doc
{
  "title":"My name is lisa"
}

GET term-search-index/_search
{
  "query": {
    "term": {
      "title": {
        "value": "My name is lisa"
      }
    }
  }
}

// 召回文档为空

2.8 terms

terms 和 term 类似,区别是terms可以匹配多值,参数是数组。如下示例:

PUT terms-search-index
{
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword"
      }
    }
  }
}

POST terms-search-index/_bulk
{"create":{"_id":"1"}}
{"name":"Jackson"}
{"create":{"_id":"2"}}
{"name":"Lisa"}

GET terms-search-index/_search
{
  "query": {
    "terms": {
      "name": [
        "Jackson",
        "Lisa"
      ]
    }
  }
}

// 召回文档
[
  {
    "_index": "terms-search-index",
    "_id": "1",
    "_score": 1,
    "_source": {
      "name": "Jackson"
    }
  },
  {
    "_index": "terms-search-index",
    "_id": "2",
    "_score": 1,
    "_source": {
      "name": "Lisa"
    }
  }
]

2.9 terms_set

terms_set 与 terms 类似,但是它可以指定必须满足一定的匹配词的数量,文档才会被召回。

比如Github代码仓库,每个仓库都可能包含多种编程语言,我们可以同时搜索多种编程语言,仓库必须匹配一定数量的编程语言才会被展示。下面的例子中,要求编程语言的匹配率达到70%,1号文档只匹配了”c++“所以没有被召回,2号文档成功召回。

PUT terms-set-search-index
{
  "mappings": {
    "properties": {
      "languages": {
        "type": "keyword"
      }
    }
  }
}

POST terms-set-search-index/_bulk
{"create":{"_id":"1"}}
{"languages":["c","c++","c#"]}
{"create":{"_id":"2"}}
{"languages":["java","python","kotlin"]}

GET terms-set-search-index/_search
{
  "query": {
    "terms_set": {
      "languages":{
        "terms":["java","kotlin","go","c++"],
        "minimum_should_match_script":{
          "source":"doc['languages'].length*0.7" // 匹配数量脚本 匹配率70%以上
        }
      }
    }
  }
}

// 召回文档
[
  {
    "_index": "terms-set-search-index",
    "_id": "2",
    "_score": 1.9061546,
    "_source": {
      "languages": [
        "java",
        "python",
        "kotlin"
      ]
    }
  }
]

2.10 wildcard

wildcard 是通配符模式匹配查询,根据指定的通配符表达式来查询文档。”*"表示匹配0或多个字符、“.”表示匹配任意单个字符。和regexp一样,wildcard在数据量较大时会导致较高的计算负担,尽量避免使用以影响Elasticsearch集群性能。

PUT wildcard-search-index
{
  "mappings": {
    "properties": {
      "product": {
        "type": "keyword"
      }
    }
  }
}

POST wildcard-search-index/_bulk
{"create":{"_id":"1"}}
{"product":"九阳豆浆机"}
{"create":{"_id":"2"}}
{"product":"北大荒豆浆粉"}

GET wildcard-search-index/_search
{
  "query": {
    "wildcard": {
      "product": {
        "value": "*豆浆*"
      }
    }
  }
}

// 召回文档
[
  {
    "_index": "wildcard-search-index",
    "_id": "1",
    "_score": 1,
    "_source": {
      "product": "九阳豆浆机"
    }
  },
  {
    "_index": "wildcard-search-index",
    "_id": "2",
    "_score": 1,
    "_source": {
      "product": "北大荒豆浆粉"
    }
  }
]

3. 全文检索

精确查询是一种根据准确词条查询文档的方法,文档匹配的结果只有两种:匹配和不匹配,这一点和关系型数据库的SQL查询类似。相比之下,全文检索就要复杂一些,它是Elasticsearch提供的一种对文档内容进行深入分析和处理的方法,它的目的是找到与查询关键词”相关“的文档,注意,这里是”相关“,而不要求文档与查询关键词完全匹配。相关性对应的就是文档的评分”_score"字段,它是一个正浮点数,评分越高代表文档相关性越高。评分是怎么来的呢?全文检索会考虑词汇的语义关联,包括词干、同义词等,再根据词汇在文档中出现的频率以及在所有文档中出现的频率等信息,利用特定的算法来计算文档相关性评分。一般来说,一个词在单个文档中出现的频率越高越相关,一个词在所有文档中出现的频率越高越不相关,代表它是一个常用词。

3.1 match

match 是分词匹配查询,它是全文检索的标准查询,适用于高召回率低准确率的场景。

text类型字段在索引时,字段值会先经过分析器分词处理后再进行存储,match查询同样也会先对搜索词进行分词后再匹配,match查询本质上可以看做是大bool查询和term查询组合而成,在保证高召回率的前提下牺牲了一定的准确性。如果分析器没有处理掉常见的停用词、助词、介词、冠词等,就会导致用户在搜索一些无意义的词时反而会召回大量的文档,虽然召回率高,但这通常是没有意义的。

如下示例,创建messages索引,其中message字段是text类型,并且使用专业的中文分析器 ik_smart。

PUT messages
{
  "mappings": {
    "properties": {
      "message":{
        "type": "text",
        "analyzer": "ik_smart"
      }
    }
  }
}

紧接着,索引一篇文档

POST messages/_bulk
{"create":{"_id":"1"}}
{"message":"五岳是泰山、华山、衡山、恒山与嵩山五座中国文化五大名山的总称,具有独特的历史文化、科学和美学价值。"}

然后开始match查询,搜索词也需要经过分析器分词,而且可以和索引时使用不同的分析器,搜索时我们就用 ik_max_word,分词的粒度会更细一些,如下示例:

GET messages/_search
{
  "profile": false, 
  "query": {
    "match": {
      "message": {
        "query": "中国有哪些名山",
        "analyzer": "ik_max_word"
      }
    }
  }
}

可以看到,搜索词和文档并没有精确匹配,匹配的仅有“中国”、“名山”这两个词,但是文档还是被召回了,这就是全文检索的魅力:

// 召回文档
[
  {
    "_index": "messages",
    "_id": "1",
    "_score": 0.2876821,
    "_source": {
      "message": "五岳是泰山、华山、衡山、恒山与嵩山五座中国文化五大名山的总称,具有独特的历史文化、科学和美学价值。"
    }
  }
]

你可能会好奇,那只匹配单个汉字 ,是不是也能召回文档呢?我们试一下,文档的最后四个字是“美学价值”,我们把搜索词换成“美术价格”,四个字里面分别匹配两个字,结果是无法召回任何文档

GET messages/_search
{
  "profile": false, 
  "query": {
    "match": {
      "message": {
        "query": "美术价格",
        "analyzer": "ik_max_word"
      }
    }
  }
}

// 召回文档
"hits": []

这就和文本分析器有关了,首先文档索引使用ik_smart分析器,它对“美学价值”的分词结果是:[“美学”,“价值”],如下所示:

POST _analyze
{
  "analyzer":"ik_smart",
  "text":"美学价值"
}

["美学","价值"]

搜索使用ik_max_word分析器,虽然分词粒度会更细,但是对于“美术价格”也没法切分的更细了,同样分出两个词:[“美术”,“价格”]

POST _analyze
{
  "analyzer":"ik_max_word",
  "text":"美术价格"
}

["美术","价格"]

因为切分后的词并不匹配,所以“美术价格”搜索词无法召回文档,就很好理解了。

3.2 match_phrase

match_phrase 是短语匹配查询,它比match查询匹配的要求更高,适用于高精准率低召回率的场景。match_phrase 除了要求词能匹配上,还要求词在文档中的顺序要一致,以确保更高的精准度。

词在文档中的顺序是什么呢?文本分析器除了分词,还会记录切分后的词在文档中的位置,字段是“position”,它是一个从0开始的正整数,如下示例:

POST _analyze
{
  "analyzer":"ik_smart",
  "text":"五岳是泰山、华山、衡山、恒山与嵩山"
}

// 分词结果
{
  "tokens": [
    {
      "token": "五岳",
      "position": 0
    },
    {
      "token": "是",
      "position": 1
    },
    {
      "token": "泰山",
      "position": 2
    },
    {
      "token": "华山",
      "position": 3
    },
    {
      "token": "衡山",
      "position": 4
    },
    {
      "token": "恒山",
      "position": 5
    },
    {
      "token": "与",
      "position": 6
    },
    {
      "token": "嵩山",
      "position": 7
    }
  ]
}

默认情况下,match_phrase 要求查询词在和文档中的词的顺序完全一致,才会召回文档,如下示例:

GET messages/_search
{
  "profile": false, 
  "query": {
    "match_phrase": {
      "message": {
        "query": "泰山华山"
      }
    }
  }
}

// 召回文档
{
  "_index": "messages",
  "_id": "1",
  "_score": 0.5753642,
  "_source": {
    "message": "五岳是泰山、华山、衡山、恒山与嵩山五座中国文化五大名山的总称,具有独特的历史文化、科学和美学价值。"
  }
}

反之,如果搜索“泰山衡山”就无法召回文档,因为中间还隔着一个“华山”。如果不想匹配条件太严格,Elasticsearch还支持配置“slop"参数来降低匹配顺序匹配的严格程度。slop 参数的意思是,允许对搜索词的位置进行一定次数的调整,使得搜索词的顺序和文档词的顺序匹配。

举个例子,搜索"泰山衡山"无法召回文档,因为中间有”华山“,于是我们可以把slop设为1,允许中间插入一个华山,即可召回文档:

GET messages/_search
{
  "profile": false, 
  "query": {
    "match_phrase": {
      "message": {
        "query": "泰山衡山",
        "slop": 1
      }
    }
  }
}

再举例,搜索"华山泰山"无法召回文档,因为它俩顺序和文档中词的顺序是反的,那么需要对搜索词进行几次位置的调整才能和文档词顺序匹配上呢?答案是2次,首先把“华山”移动到1号位,再把“泰山”移动到0号位。因此,把slop设为2即可召回文档:

GET messages/_search
{
  "profile": false, 
  "query": {
    "match_phrase": {
      "message": {
        "query": "华山泰山",
        "slop": 2
      }
    }
  }
}

match_phrase 的匹配难度显然要比match高,它不仅要匹配词,还要匹配词出现的顺序,多个词在一起形成短语便有了语义,在追求高准确率的召回场景时建议优先使用 match_phrase 查询类型。

3.3 match_phrase_prefix

match_phrase_prefix 是短语前缀匹配查询,它结合了短语查询和前缀查询的特点,它和match_phrase的区别是,允许最后一个词只匹配前缀即可。

如下示例,索引一篇文档:

POST messages/_bulk
{"create":{"_id":"2"}}
{"message":"目前非常流行的数据分析引擎Elasticsearch发布新的版本,带来更多新特性"}

当用户无法拼出完整的“Elasticsearch”单词时,使用match_phrase_prefix查询也可以召回文档:

GET messages/_search
{
  "profile": false, 
  "query": {
    "match_phrase_prefix": {
      "message": {
        "query": "数据分析引擎Elastic"
      }
    }
  }
}

// 召回文档
[
  {
    "_index": "messages",
    "_id": "2",
    "_score": 1.7729156,
    "_source": {
      "message": "目前非常流行的数据分析引擎Elasticsearch发布新的版本,带来更多新特性"
    }
  }
]

3.4 query_string

query_string 查询类型允许用户直接使用Lucene表达式来构建复杂的查询请求,是一种功能十分强大的查询类型,同时会使用严格的语法解析器来解析查询表达式,表达式错误将会抛出异常,一般不建议直接将用户输入的内容作为查询表达式直接检索。

如下示例,创建books索引并索引一些文档:

PUT books
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "publisher":{
        "type": "keyword"
      },
      "price":{
        "type": "long"
      }
    }
  }
}

POST books/_bulk
{"create":{"_id":"1"}}
{"title":"深入理解计算机系统","publisher":"机械工业出版社","price":23900}
{"create":{"_id":"2"}}
{"title":"被讨厌的勇气","publisher":"机械工业出版社","price":3980}
{"create":{"_id":"3"}}
{"title":"卓有成效的管理者","publisher":"机械工业出版社","price":2800}

接下来,我们用 query_string 检索:机械工业出版社 出版的 标题含”计算机“或价格小于30元的书,一个查询表达式直接搞定,否则就要编写复杂的bool查询。

GET books/_search
{
  "query": {
    "query_string": {
      "query": """
      publisher:机械工业出版社 AND (title:计算机 OR price:<=3000)
      """
    }
  }
}

// 召回文档
[
  {
    "_index": "books",
    "_id": "3",
    "_score": 1.1335313,
    "_source": {
      "title": "卓有成效的管理者",
      "publisher": "机械工业出版社",
      "price": 2800
    }
  },
  {
    "_index": "books",
    "_id": "1",
    "_score": 1.0791914,
    "_source": {
      "title": "深入理解计算机系统",
      "publisher": "机械工业出版社",
      "price": 23900
    }
  }
]

关于 query_string 的更多语法,可以参考官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/8.13/query-dsl-query-string-query.html#query-string-syntax。

3.5 simple_query_string

simple_query_string 可以看作是 query_string 的简化版本,它同样基于查询表达式检索,但是支持的语法有限,不使用严格的语法解析器,即使语法错误也不会报错。支持的语法详见文档:https://www.elastic.co/guide/en/elasticsearch/reference/8.13/query-dsl-simple-query-string-query.html#simple-query-string-syntax

如下示例,查询机械工业出版社的标题中含有”计算机“或”勇气“的书:

GET books/_search
{
  "query": {
    "simple_query_string": {
      "query": "机械工业出版社 + (计算机 | 勇气)",
      "fields": ["title","publisher"]
    }
  }
}

// 召回文档
[
  {
    "_index": "books",
    "_id": "1",
    "_score": 1.0791914,
    "_source": {
      "title": "深入理解计算机系统",
      "publisher": "机械工业出版社",
      "price": 23900
    }
  },
  {
    "_index": "books",
    "_id": "2",
    "_score": 1.0791914,
    "_source": {
      "title": "被讨厌的勇气",
      "publisher": "机械工业出版社",
      "price": 3980
    }
  }
]

4. 组合查询

Elasticsearch 还支持 Compound queries 组合查询,可以将上述的查询类型自由组合,以构建复杂的查询请求。

4.1 bool

bool 组合查询是一种灵活且强大的查询方式,复杂的查询请求通常由一系列子查询组成,且子查询之间通过”与或非“的关系连接。例如:查询机械工业出版社出版的,标题含”计算机“或”编程“,且作者不是”张三“的图书,这个复杂的查询就可以拆分成如下表示:

publisher=机械工业出版社 AND (title:计算机 OR title:编程) NOT author:张三

bool 组合查询支持四种子查询类型:

  • must:必须满足的条件,相当于 AND
  • filter:过滤条件,相当于 NOT,应该满足的最少条件数量由minimum_should_match指定
  • should:应该满足的部分条件,相当于 OR
  • must_not:不能满足的条件,相当于 NOT

filter和must_not都是设置不能满足的条件,也均不参与文档相关性评分,区别是filter有缓存机制,可以提升查询性能。

如下示例,创建books索引并索引一些文档:

PUT books
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "publisher":{
        "type": "keyword"
      },
      "author":{
        "type": "keyword"
      }
    }
  }
}

POST books/_bulk
{"create":{"_id":"1"}}
{"title":"深入理解计算机系统","publisher":"机械工业出版社","author":"Randal E. Bryant"}
{"create":{"_id":"2"}}
{"title":"被讨厌的勇气","publisher":"机械工业出版社","author":"岸见一郎"}
{"create":{"_id":"3"}}
{"title":"不存在的书","publisher":"机械工业出版社","author":"张三"}

使用 bool 组合查询,实现上述查询需求,如下所示:

GET books/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "publisher": {
              "value": "机械工业出版社"
            }
          }
        },
        {
          "bool": {
            "should": [
              {
                "match": {
                  "title": "计算机"
                }
              },
              {
                "match": {
                  "title": "编程"
                }
              }
            ]
          }
        }
      ],
      "must_not": [
        {
          "term": {
            "author": {
              "value": "张三"
            }
          }
        }
      ]
    }
  }
}

// 召回文档
[
  {
    "_index": "books",
    "_id": "1",
    "_score": 1.0791914,
    "_source": {
      "title": "深入理解计算机系统",
      "publisher": "机械工业出版社",
      "author": "Randal E. Bryant"
    }
  }
]

4.2 boosting

boosting 查询可以在召回文档的同时,指定一个 negative 查询条件来降低文档的相关性评分。举个例子,电商系统根据用户输入的关键字搜索商品时,希望可以把差评多、退货率高的商品尽可能排在后面展示。

boosting 查询需要三个参数:

  • positive:正面查询,召回的文档必须匹配该查询条件
  • negative:负面查询,匹配该条件的文档相关性评分会降低
  • negative_boost:匹配negative查询条件的文档降低评分的比例,0到1.0之间则是降低评分,大于1.0则是提高评分

如下示例,查询所有机械工业出版社的图书,但是降低作者是“张三”的文档评分:

GET books/_search
{
  "query": {
    "boosting": {
      "positive":{
        "term": {
          "publisher": {
            "value": "机械工业出版社"
          }
        }
      },
      "negative":{
        "term": {
          "author": {
            "value": "张三"
          }
        }
      },
      "negative_boost":0.5
    }
  }
}

张三的文档依然会被召回,但是评分是其它书的一半

[
  {
    "_index": "books",
    "_id": "1",
    "_score": 0.13353139,
    "_source": {
      "title": "深入理解计算机系统",
      "publisher": "机械工业出版社",
      "author": "Randal E. Bryant"
    }
  },
  {
    "_index": "books",
    "_id": "2",
    "_score": 0.13353139,
    "_source": {
      "title": "被讨厌的勇气",
      "publisher": "机械工业出版社",
      "author": "岸见一郎"
    }
  },
  {
    "_index": "books",
    "_id": "3",
    "_score": 0.066765696,
    "_source": {
      "title": "不存在的书",
      "publisher": "机械工业出版社",
      "author": "张三"
    }
  }
]

4.3 constant_score

constant_score 是常量分数查询,它本质上是包装了一个过滤器,不计算文档的相关性评分,评分由参数boost指定,召回的所有文档评分都是一样的。

如下示例,查询标题中包含“计算机”或“勇气”的书,召回的文档评分都是指定的3.8

GET books/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "match":{
          "title":"计算机 勇气"
        }
      },
      "boost": 3.8
    }
  }
}

// 召回文档
[
  {
    "_index": "books",
    "_id": "1",
    "_score": 3.8,
    "_source": {
      "title": "深入理解计算机系统",
      "publisher": "机械工业出版社",
      "author": "Randal E. Bryant"
    }
  },
  {
    "_index": "books",
    "_id": "2",
    "_score": 3.8,
    "_source": {
      "title": "被讨厌的勇气",
      "publisher": "机械工业出版社",
      "author": "岸见一郎"
    }
  }
]

4.4 dis_max

dis_max 查询可以设置多个子查询条件,文档只要满足任一条件即可被召回,满足的查询条件越多,文档的相关性评分越高。

dis_max 查询参数有两个:

  • queries:查询条件数组
  • tie_breaker:用于提高满足多个查询条件时文档的评分比例

如下示例,我们设置三个匹配词,匹配的越多,文档的相关性评分就越高,1号文档因为匹配了两个词,所以评分是2号文档的两倍:

GET books/_search
{
  "query": {
    "dis_max": {
      "tie_breaker": 1.0,
      "queries": [
        {
          "match": {
            "title": "计算机"
          }
        },
        {
          "match": {
            "title": "系统"
          }
        },
        {
          "match": {
            "title": "勇气"
          }
        }
      ]
    }
  }
}

// 召回文档
[
  {
    "_index": "books",
    "_id": "1",
    "_score": 1.89132,
    "_source": {
      "title": "深入理解计算机系统",
      "publisher": "机械工业出版社",
      "author": "Randal E. Bryant"
    }
  },
  {
    "_index": "books",
    "_id": "2",
    "_score": 0.94566,
    "_source": {
      "title": "被讨厌的勇气",
      "publisher": "机械工业出版社",
      "author": "岸见一郎"
    }
  }
]

4.5 function_score

function_score 是自定义文档评分查询,它允许我们修改Elasticsearch给文档计算的相关性评分。

文档相关性评分是很重要的,一般来说相关性评分越高,代表文档越符合用户搜索的期望,一般会更靠前展示。Elasticsearch默认使用TF-IDF算法根据搜索词在文档中出现的频率以及在所有文档中出现的频率来计算相关性评分,但在有些时候,这个算法可能并不满足我们的需求,不是所有场景都是根据词频的高低来判断相关性的,比如电商系统中,除了商品标题的匹配,商品的销量也是一个很重要的因素。当默认的相关性评分算法不满足需求时,就可以通过 function_score 查询来自定义文档评分。

function_score 主要由一个查询和一组函数构成,参数如下:

  • query:查询条件,文档必须匹配才会被召回
  • functions:自定义评分函数数组
  • boost:对评分二次计算的值
  • boost_mode:二次计算的分数和原始分数的结合模式,比如是相加还是相乘
  • score_mode:各个函数计算的分数结合模式,比如是相加还是相乘

function_score 查询支持的函数类型:

  • script_score:自定义脚本函数
  • weight:将原始分数再乘以给定的权重
  • random_score:生成0~1区间的随机分数
  • field_value_factor:使用文档中的字段来计算分数
  • DECAY:衰减评分函数

如下示例,创建items索引并索引一些文档:

PUT items
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "sales":{
        "type": "integer"
      }
    }
  }
}

POST items/_bulk
{"create":{"_id":"1"}}
{"title":"红苹果","sales":500}
{"create":{"_id":"2"}}
{"title":"青苹果","sales":1000}
{"create":{"_id":"3"}}
{"title":"普通苹果","sales":10000}

当用户搜索”红苹果“时,如果不做特殊处理,那么毋庸置疑,1号文档的评分肯定是最高的。此时,我们想让商品的销量也参与到文档评分计算中,尽可能提升高销量商品的评分,此时的 function_score 如下所示:

GET items/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": {
          "title": "红苹果"
        }
      },
      "functions": [
        {
          "script_score": {
            "script":{
              "source": "Math.log(doc['sales'].value)"
            }
          }
        }
      ],
      "boost_mode": "sum",
      "score_mode": "sum"
    }
  }
}

我们在原始评分的基础上,引入了商品销量因素,script_score 把商品销量取对数计算一个新的评分,之所以取对数,是避免高销量对评分的影响过大,然后让二者相加,最终召回的文档如下:

[
  {
    "_index": "items",
    "_id": "3",
    "_score": 9.261634,
    "_source": {
      "title": "普通苹果",
      "sales": 10000
    }
  },
  {
    "_index": "items",
    "_id": "1",
    "_score": 7.3157234,
    "_source": {
      "title": "红苹果",
      "sales": 500
    }
  },
  {
    "_index": "items",
    "_id": "2",
    "_score": 6.9590487,
    "_source": {
      "title": "青苹果",
      "sales": 1000
    }
  }
]

普通苹果 的标题虽然不是匹配度最高的,但是因为销量高所以评分最高;青苹果 的销量虽然比 红苹果高,但是差别不大,因为 红苹果 的标题匹配度更高,所以评分比 青苹果 高也是合理的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2217220.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文生图:Stable Diffusion、Midjourny

前言 Stable Diffusion&#xff08;SD&#xff09;和Midjourney&#xff08;MJ&#xff09;是当前流行的两款AI图像生成工具&#xff0c;它们各有特点和优势&#xff1a; **- Stable Diffusion是完全开源的&#xff0c;**这意味着用户可以免费使用&#xff0c;并且有技术能力…

excel如何把年龄转换为日期

总体的思路 我们从一个核心的日期函数出发 我们首先需要年月日 我的数据大概是这样的。 获取年份 第一步&#xff1a;提取岁前面的数字 left(目标单元格&#xff0c;“从左到右获取第几个字符”)第二步:替换掉数字后面的岁 第三步:新增一个单元格 在里面填入年 第四步:用…

Android系統Audio hal

一.Android系統Audio hal简介 Android系统的音频硬件抽象层(HAL)是系统与硬件之间的桥梁,允许音频应用和服务访问底层音频硬件,而无需直接与硬件交互。 主要组件: 音频 HAL 接口:定义了应用和服务如何调用音频硬件的规范。典型的音频操作包括播放、录制、音量控制等。 …

N1060A 50/85GHz精密型波形分析模块

N1060A 50/85GHz精密型波形分析模块 苏州新利通 概述 Keysight N1060A 精密型波形分析仪是一款数字通信分析仪&#xff08;DCA&#xff09;模块&#xff0c;可与 Keysight N1000A 主机兼容。 与是德科技的所有其他 DCA 模块一样&#xff0c;N1060A 提供了广泛的配置和性能选…

【C语言】数组函数冒泡排序bubble sort

数组&#xff1a;对于n个数字进行排序&#xff0c;就必须定义n个变量来存储。那么为了统一处理&#xff0c;选择数组就十分便捷了。 函数&#xff1a;将排序算法写到函数中&#xff0c;后续遇到所有的排序需求&#xff0c;都可以直接进行调用。 冒泡排序&#xff1a;受气泡在水…

HDFS详细分析

目录 一、HDFS架构 &#xff08;1&#xff09;Block - 数据块 &#xff08;2&#xff09;MetaData - 元数据 &#xff08;3&#xff09;NameNode - 主结点 &#xff08;4&#xff09;DataNode - 从结点 &#xff08;5&#xff09;SecondaryNameNode 二、HDFS的特点 &…

【19楼-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

2024年下软考——信息系统运行管理员考前30天冲刺学习指南!!!

2024下半年软考已经迫在眉睫了&#xff0c;还没有开始备考的小伙伴赶紧行动起来。为了帮助大家更好的冲刺学习&#xff0c;特此提供一份信息系统运行管理员考前30天学习指南。本指南包括考情分析、学习规划、冲刺攻略三个部分&#xff0c;可以参考此指南进行最后的复习要领&…

javaWeb项目-Springboot+vue-校园论坛系统功能介绍

本项目源码&#xff08;点击下方链接下载&#xff09;&#xff1a;java-springbootvue-xx学校校园论坛信息系统实现源码(项目源码-说明文档)资源-CSDN文库 项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot…

鼠标移入盒子,盒子跟随鼠标移动

demo效果&#xff1a; 鼠标移入盒子&#xff0c;按下鼠标,开启移动跟随移动模式,再次按下关闭移动模式 涉及主要属性 在元素上单击鼠标按钮时输出鼠标指针的坐标&#xff1a; var x event.pageX; // 获取水平坐标 var y event.pageY; // 获取垂直坐标元素offsetL…

【含开题报告+文档+PPT+源码】基于SSM的景行天下旅游网站的设计与实现

开题报告 随着互联网的快速发展&#xff0c;旅游业也逐渐进入了数字化时代。作为一个旅游目的地&#xff0c;云浮市意识到了互联网在促进旅游业发展方面的巨大潜力。为了更好地推广云浮的旅游资源&#xff0c;提高旅游服务质量&#xff0c;云浮市决定开发一个专门的旅游网站。…

【python】用tk做一个简单的商品搜索更新展示的桌面应用

import tkinter as tk from tkinter import ttk import pandas as pddata_list [{id:1,name: 苹果, price: 6.5,tag:水果},{id:2,name: 香蕉, price: 2.5,tag:水果},{id:3,name: 葡萄, price: 8.5,tag:水果},{id:4,name: 橘子, price: 4.5,tag:水果}, ]df pd.DataFrame(data_…

SwiftUI 如何取得 @Environment 中 @Observable 对象的绑定?

概述 从 SwiftUI 5.0&#xff08;iOS 17&#xff09;开始&#xff0c;苹果推出了全新的 Observation 框架。它作为下一代内容改变响应者全面参与到数据流和事件流的系统中。 有了 Observation 框架的加持&#xff0c;原本需要多种状态类型的 SwiftUI 视图现在只需要 3 种即可大…

海康NVR管理平台EasyNVR多品牌NVR管理工具实现智能化视频管理介入现代化工厂

一、方案背景 在当今这个日新月异的时代&#xff0c;制造业作为国民经济的支柱之一&#xff0c;正经历着前所未有的变革。随着信息技术的飞速发展&#xff0c;工厂的现代化管理手段准确性也越来越高&#xff0c;越来越丰富&#xff0c;各种先进的技术手段比如视频监控系统&…

R语言医学数据分析实践-R编程环境的搭建

【图书推荐】《R语言医学数据分析实践》-CSDN博客 《R语言医学数据分析实践 李丹 宋立桓 蔡伟祺 清华大学出版社9787302673484》【摘要 书评 试读】- 京东图书 (jd.com) R语言编程_夏天又到了的博客-CSDN博客 R语言对编程环境的要求不高&#xff0c;可以在多种操作系统平台上…

对于SOCKS协议的一些认知误区有哪些?

代理协议在设备与代理服务器之间的数据交换中起到了关键作用。在这方面&#xff0c;SOCKS代理协议是常见的选择之一&#xff0c;被广泛应用于下载、传输和上传网络数据的场景。然而&#xff0c;关于SOCKS代理协议存在一些常见的误解&#xff0c;让我们来逐一了解。 一、使用SO…

nRF54L15—蓝牙低功耗双核系统级芯片(SoC)

nRF54L15 是 nRF54L 系列的首款系统级芯片 (SoC)。它是一款超低功耗蓝牙 5.4 SoC&#xff0c;具有同类最佳的新型多协议无线电和先进的安全功能。nRF54L 系列以更紧凑的封装将广受欢迎的 nRF52 系列提升到新的水平&#xff0c;具有出色的处理能力和效率、扩展的内存和新型外设。…

开发 - develop-codescan-zwcz53

开发 - develop-codescan-zwcz53 1. 开发 - CodeScan2. 前言3. CodeScan3.1. 工具概述3.2. 编译3.3. 功能3.4. 使用3.5. 高级用法3.5.1. 高扩展性3.5.2. 扫描位置3.5.3. 过滤字符串(只写了JSP PHP)3.5.4. 静态分析依赖情况 3.6. TODO3.7. 支持项目3.8. 详细使用文章(内附案例)…

运维怎么转行网络安全?

经常有人问我&#xff1a;干网工、干运维多年遇瓶颈&#xff0c;想学点新技术给自己涨涨“身价”&#xff0c;应该怎么选择&#xff1f; 聪明人早已经用脚投票&#xff1a;近年来&#xff0c;越来越多运维的朋友寻找新的职业发展机会&#xff0c;将目光聚焦到了网络安全产业。…

大数据-174 Elasticsearch Query DSL - 全文检索 full-text query 匹配、短语、多字段 详细操作

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…