Elasticsearch之Search

  1. ES的Search选项
  2. Query DSL
    1. Query and filter context
      1. Relevance scores(相关性分数)
      2. Query context(查询上下文)
      3. Filter context(过滤器上下文)
    2. Compound queries
      1. Boolean query
      2. Boosting query
      3. Constant score query
    3. Full text queries
      1. Intervals query
      2. Match query
        1. query短查询
        2. operator参数
        3. fuzziness模糊查询
      3. Match boolean prefix query
      4. Match phrase query
      5. Match phrase prefix query
      6. Combined fields查询
      7. Multi-match query
      8. Query string query
    4. Term-level查询
      1. Exists query
      2. Fuzzy query
      3. IDs query
      4. Prefix query
      5. Range query
      6. Term query
      7. Terms query
      8. Terms set query

我们使用Elasticsearch的最终目的是从数据集中搜索想要的数据,并基于这些数据建立视图;Elasticsearch提供了很简单易用的查询方式来获得数据,例如:

  • 我的服务中有哪些请求处理耗时超过500ms的;
  • 在我的集群中,上一周有哪些用户执行了regsvr32.exe
  • 我的网站中哪些页面包含了特殊的文字或者段落;

Elasticsearch总的来说提供了以下几种搜索方式:

那我们改如何构建Elasticsearch的搜索请求语句呢?本文接下来就根据官方文档来阐述ES如何搜索数据的。

ES的Search选项

Elasticsearch一个search可以由一个或者多个query组成,Elasticsearch会返回匹配的documents,一个search也可以包含额外用于更好进行query的信息,例如限制返回的查询结果数;

ES本身支持的搜索选项有很多,我们可以通过Search APIs 来搜索和聚合数据;ES的一个search可以支持如下一些通用的选项:

Query DSL是Elasticsearch提供的基本查询表达式,支持通过DSL(Domain Specific Language)领域特定语言,一种基于JSON的表达式语法来进行数据的查询;Query DSL提供了丰富多样的query类型,可以通过组合去获取我们想要的查询结果,query支持的类型包括:

  1. Compound queries复合查询:允许组合多个条件查询来生成匹配结果,例如Boolean 逻辑查询;
  2. Term-level queries词条级查询,基于Team的查询,用于过滤和精确匹配;
  3. Full text queries全文检索:类似搜索引擎,查询最匹配的文档;
  4. Geo坐标和spatial queries空间查询;

我们可以在search中通过aggs选项来对query结果或者直接对所有document进行聚合展示,例如:服务的平均响应时间;

在同一个请求中使用逗号分隔的值和类grep的索引模式来搜索多个数据流和索引。您甚至可以增加来自特定索引的搜索结果的权重,如下可以通过逗号分隔多个索引来在多个索引中进行同样的search

1
2
GET /love-music-idx,love-music-idx1/_search
<!--QUEST BODY-->

默认情况,针对一个search,最多返回top10的匹配的document;可以通过search的分页选项功能来增加或者减少搜索返回的结果数;分页支持的选项有:

选项 说明
from 跳过指定数量的hits的document,默认为0
size 最大返回hits的document的数量,默认为10
search_after

如下,从匹配的第5个document起,最多返回20个匹配结果:

1
2
3
4
5
6
GET /_search
{
"from": 5,
"size": 20,
"query": {--QUERY BODY--}
}

默认,search的返回结果会包含hits的document的所有fields,即hits.hits._source;我们可以显示的设置只返回hits document的部分fields,通过如下两个search的选项:

  1. fields选项:提取document和index mapping中的指定字段;推荐使用
  2. _source选项:

默认情况下,search的返结果会按照搜索的相关性分数即_score来进行排序,有两种方式可以改变默认的排序行为:

  1. 我们也可以在搜索时,在search中通过script_score选项来定制自己的_score计算公式;此种方式还是现在_score进行排序;
  2. 我们也可以通过按照document的指定fields进行对搜索结果的排序;

接下来我们就详细展开Elasticsearch的一个search可以支持的通用的选项

Query DSL

Elasticsearch提供了一个Query DSL(Domain Specific Language),直翻为:查询领域特定语言,是一种非常灵活又富有表现力的基于JSON的查询语言。 Elasticsearch 使用它可以以简单的 JSON 接口来展现 Lucene 功能的绝大部分。

DSL查询语句可以看作是有两种类型的子句组成的查询的AST(Abstract Syntax Tree)抽象语法树:分为两类:

  • Leaf query clauses(叶子查询子句):用于查询特定字段的特定值,例如matchtermrange查询,这些查询可以单独使用;
  • Compound query claused(复合查询子句):由一系列的叶子查询或者复合查询组成的,用以逻辑方式(booldis_max)组合的多个查询或者改变默认的查询;

DSL查询语句的行为根据是否是query context或是filter context会有不同表现;官方文档 Allow expensive queries中列出来,比较耗时的查询类型;

Query and filter context

前面提到:DSL查询语句的行为根据是否是query context或是filter context会有不同表现,下面介绍查询和过滤器上下文相关的概念:

Relevance scores(相关性分数)

相关性分数表示查询和document的匹配程度,默认情况下,Elasticsearch会根据查询结果的各个document的相关性分数进行排序;

相关性分数一个浮点数,在搜索结果中通过元数据字段_score展示,该数值越大关联度越高,相关性分数数值范围在 0 到 1 之间,在 Elasticsearch 中,相关性分数是通过一种称为 TF/IDF(Term Frequency/Inverse Document Frequency)的算法计算的。TF/IDF 算法考虑了词频(term frequency)和逆文档频率(inverse document frequency)来计算文档与查询之间的相关性。

每一种类型的查询计算相关性分数都是不同的,(Query Context)过滤器上下文(Filter Context)中相关性分数的计算方式是不同的

如下测试:

1
2
3
4
5
6
7
8
9
10
11
12
PUT my-index-000001/_doc/1
{
"name": "walkerdu",
"description": "this is a handsome guy",
"age": 18,
"email": "walkerdualias@gmail.com",
"@timestamp": "2023-05-01T10:25:43.592Z",
"family": {
"wife": "A",
"son": "B"
}
}

查询:

1
2
3
4
5
6
GET my-index-000001/_search
{
"query": {
"match": {"description": "handsome guy"}
}
}

结果如下,可以看到hits中,匹配的文档中返回了"_score": 0.5753642

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"took": 3, "timed_out": false,
"_shards": {"total": 1, "successful": 1, "skipped": 0, "failed": 0},
"hits": {
"total": {"value": 1, "relation": "eq"},
"max_score": 0.5753642,
"hits": [{
"_index": "my-index-000001", "_id": "1", "_score": 0.5753642,
"_source": {
"name": "walkerdu",
"description": "this is a handsome guy",
"age": 18,
"email": "walkerdualias@gmail.com",
"@timestamp": "2023-05-01T10:25:43.592Z",
"family": {"wife": "A", "son": "B"}
}
}]
}
}

Query context(查询上下文)

查询上下文中,查询子句回答的是:查询语句和文档的匹配度。除了判断document和查询是否匹配,查询子句还要计算出一个relevance socre相关性分数

查询上下文的语法结构是在_search语句中,查询参数以query参数来标识,如下,可以参考Search API

1
2
3
4
GET /my-index-000001/_search
{
"query" : {...}
}

如下,基于上面相关性分数的测试,再往Index中插入一个doc2,

1
2
3
4
5
6
7
8
9
10
11
12
PUT my-index-000001/_doc/2
{
"name": "alias",
"description": "a handsome guy",
"age": 18,
"email": "alias@gmail.com",
"@timestamp": "2023-05-01T10:25:43.592Z",
"family": {
"wife": "A",
"son": "B"
}
}

执行查询如下:

1
2
3
4
5
6
7
8
GET my-index-000001/_search
{
"query": {
"match": {
"description": "handsome guy"
}
}
}

可以看到返回结果中每个doc都会计算相关新分数,并按分数进行排序返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
"took": 1, "timed_out": false,
"_shards": {"total": 1, "successful": 1, "skipped": 0, "failed": 0},
"hits": {
"total": {"value": 1, "relation": "eq"},
"max_score": 0.40618476,
"hits": [{
"_index": "my-index-000001", "_id": "2", "_score": 0.40618476,
"_source": {
"name": "alias",
"description": "a handsome guy",
"age": 18,
"email": "alias@gmail.com",
"@timestamp": "2023-05-01T10:25:43.592Z",
"family": {
"wife": "A",
"son": "B"
}
}
},
{
"_index": "my-index-000001", "_id": "1", "_score": 0.33081025,
"_source": {
"name": "walkerdu",
"description": "this is a handsome guy",
"age": 18,
"email": "walkerdualias@gmail.com",
"@timestamp": "2023-05-01T10:25:43.592Z",
"family": {
"wife": "A",
"son": "B"
}
}
}]}
}

Filter context(过滤器上下文)

过滤器上下文中,查询子句回答的是:这个文档是否匹配该查询子句。返回结果就是Yes or No,没有相关性分数的计算;过滤器上下文主要用于过滤结构化数据,比如:

  • 某个timestamp是否落在2015年到2016年的范围内?
  • 某个status字段是否设置为"published"?

Elasticsearch会自动缓存频繁使用的过滤器,以提高性能。

过滤器上下文生效的方式是在query子句中通过传入filter参数,例如:bool查询中的filter或者must_not参数,constant_score查询中的filter参数,或者filter聚合查询

如下:

1
2
3
4
5
6
7
8
9
10
GET my-index-000001/_search
{
"query": {
"bool": {
"filter": {
"term": {"name": "alias"}
}
}
}
}

该查询由于使用了过滤器上下文,所以所有匹配的name字段的所有document都不会计算相关性分数,如下是返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
...
"hits": {
...
"max_score": 0,
"hits": [
{
"_index": "my-index-000001", "_id": "2", "_score": 0,
"_source": {
"name": "alias",
...
}
}
]
}
}

Compound queries

Compound queries复合查询:由一系列的叶子查询或者复合查询组成的,用以组合的多个查询或者改变默认的查询,或者切换query成为filter上下文。复合查询主要一下几类查询组成:

  • bool query:这是组合多个叶子查询或复合查询的默认方式,通过mustshouldmust_notfilter等子句进行组合。mustshould子句的分数会被合并,更多匹配会得到更高分数,而must_notfilter子句运行在过滤器上下文中。
  • boosting query:返回匹配positive查询的文档,但会降低也同时匹配negative查询的文档分数。
  • constant_score query:包装另一个查询,但在过滤器上下文中执行。所有匹配的文档会被赋予相同的常量_score
  • dis_max query:接受多个查询,返回匹配任何一个查询子句的文档。与bool查询将所有匹配查询的分数合并不同,dis_max只使用单个最佳匹配查询子句的分数。
  • function_score query:通过函数修改主查询返回的分数,以考虑流行度、最近度、距离或使用脚本实现的自定义算法等因素。

Boolean query

bool查询是用于逻辑匹配要查询的文档,它使用一个或多个如下的bool子句构建组成:

子句 描述
must 该子句(查询)必须出现在匹配的文档中,并会对分数产生贡献。
filter 该子句(查询)必须出现在匹配的文档中。但与must不同,查询的分数将被忽略。filter子句在过滤器上下文中执行,这意味着忽略评分,并且子句会被考虑进行缓存。
should 该子句(查询)应该出现在匹配的文档中,并会对分数产生贡献。
must_not 该子句(查询)不得出现在匹配的文档中。子句在过滤器上下文中执行,这意味着忽略评分,并且子句会被考虑进行缓存。由于忽略评分,因此所有文档的分数都将返回0

bool查询采用的策略是more-matches-is-better,因此每个匹配的must或should子句的分数将被加在一起,为每个文档提供最终的_score分数

我们下面通过一个bool查询来看一下子句的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
POST _search
{
"query": {
"bool" : { // bool复合查询语句的参数
"must" : { // bool查询语句的must子句,表示doc的"user.id"的值必须是"kimchy"
"term" : { "user.id" : "kimchy" }
},
"filter": { // bool查询语句的filter子句,在filter context中执行,不会计算score,表示doc的"tags"字段的值必须是"production"
"term" : { "tags" : "production" }
},
"must_not" : {// bool查询语句的must_not子句, 在filter context中执行,不会计算score,表示doc的"age"字段的必须是>=10 && <= 20
"range" : {
"age" : { "gte" : 10, "lte" : 20 }
}
},
"should" : [ // bool查询语句的should子句,"tags"字段的值,可以是"env1"或者是"deployed"
{ "term" : { "tags" : "env1" } },
{ "term" : { "tags" : "deployed" } }
],

// bool查询语句的"minimum_should_match",表示"should"子句中最小需要匹配的子句数量,
"minimum_should_match" : 1,
"boost" : 1.0
}
}
}

Boosting query

boosting查询用于返回匹配positive查询的文档,但会降低同时匹配negative查询的文档的相关性分数。boosting查询的设计目的是从搜索结果中降级指定的文档,体现了结果排序时不同因素的权重。

boosting查询语句支持的顶级参数如下:

参数 描述
positive (必需,查询对象) 你希望运行的查询。返回的文档必须匹配这个查询。
negative (必需,查询对象) 用于降低匹配文档相关性分数的查询。
negative_boost (必需,浮点数) 介于0和1.0之间的浮点数,用于降低匹配negative查询的文档的相关性分数。

如下测试语句:

1
2
3
4
5
6
7
8
9
10
11
GET my-index-000001/_search
{
"query": {
"boosting": {
"positive": {"term": {"age": 18}},
"negative": {"term": {"name": "alias"}
},
"negative_boost": 0.5
}
}
}

如下是返回结果:可见"name": "alias"的doc的相关性分数被降低了0.5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
"took": 7,
"timed_out": false,
"_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 },
"hits": {
"total": { "value": 2, "relation": "eq" },
"max_score": 1,
"hits": [
{
"_index": "my-index-000001",
"_id": "1",
"_score": 1,
"_source": {
"name": "walkerdu",
"description": "this is a handsome guy",
"age": 18,
"email": "walkerdualias@gmail.com",
"@timestamp": "2023-05-01T10:25:43.592Z",
"family": { "wife": "A", "son": "B" }
}
},
{
"_index": "my-index-000001",
"_id": "2",
"_score": 0.5,
"_source": {
"name": "alias",
"description": "a handsome guy",
"age": 18,
"email": "alias@gmail.com",
"@timestamp": "2023-05-01T10:25:43.592Z",
"family": { "wife": "A", "son": "B" }
}
}
]
}
}

Constant score query

包装另一个查询,但在过滤器上下文中执行。所有匹配的文档会被赋予相同的常量_score分,使用方式为在query语句中使用constant_score子句来进行查询的包装,constant_score子句包含的顶层参数为:

参数 描述
filter (必需,查询对象) 你希望运行的过滤器查询。返回的文档必须匹配这个查询。
boost (可选,浮点数) 用作每个匹配 filter 查询的文档的常量相关性分数的浮点数。默认为 1.0。

如下测试:

1
2
3
4
5
6
7
8
9
10
11
GET my-index-000001/_search
{
"query": {
"constant_score": {
"filter": {
"term": { "name": "walkerdu" }
},
"boost": 888
}
}
}

测试结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"took": 0,
"timed_out": false,
"_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 },
"hits": {
"total": { "value": 1, "relation": "eq" },
"max_score": 888,
"hits": [
{
"_index": "my-index-000001",
"_id": "1",
"_score": 888,
"_source": {
"name": "walkerdu",
"description": "this is a handsome guy",
"age": 18,
"email": "walkerdualias@gmail.com",
"@timestamp": "2023-05-01T10:25:43.592Z",
"family": { "wife": "A", "son": "B" }
}
}
]
}
}

Full text queries

全文检索允许你搜索已经被分析过的文本字段,例如电子邮件正文。查询字符串会使用与索引字段时相同的分析器进行处理。全文检索查询包含以下支持的查询子句:

查询 描述
intervals 一种全文本查询,允许精细控制匹配词条的顺序和邻近程度。
match 执行全文本查询的标准查询,包括模糊匹配、短语或邻近查询。
match_bool_prefix 创建一个布尔查询,每个词条作为term查询匹配,除了最后一个词条作为prefix查询匹配。
match_phrase 类似于match查询,但用于匹配精确短语或词邻近匹配。
match_phrase_prefix 类似于match_phrase查询,但对最后一个词执行通配符搜索。
multi_match match查询的多字段版本。
combined_fields 跨多个字段进行匹配,就像它们被索引到一个组合字段中一样。
query_string 支持紧凑的Lucene查询字符串语法,允许在单个查询字符串中指定AND|OR|NOT条件和多字段搜索。仅供专家用户使用。
simple_query_string query_string语法的一个更简单、更健壮的版本,适合直接暴露给用户。

Intervals query

Intervals间隔查询是Elasticsearch中一种特殊的全文本查询方式,它不仅关注词条本身是否匹配,更关注词条在文本中的相对位置和顺序。直白的理解这句话可能还是不清楚Intervals查询到底是干什么用的,我们来看一个官方的搜索用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
POST _search
{
"query": {
"intervals" : { // 全文检索查询的intervals查询子句
"my_text" : { // intervals查询要检索的字段名
"all_of" : { // 必须满足其中包含的所有子间隔规则
"ordered" : true,// 子间隔必须按照指定的顺序匹配
"intervals" : [ // 子间隔
{
"match" : { // 该子间隔的match规则,精确匹配短语 "my favorite food"
"query" : "my favorite food",
"max_gaps" : 0, // 不允许匹配短语有任何间隔词条
"ordered" : true // 词条顺序保证顺序
}
},
{
"any_of" : { // 该子间隔的any_of规则,表示只需满足其中一个子规则即可
"intervals" : [
{ "match" : { "query" : "hot water" } }, // 匹配"hot water"
{ "match" : { "query" : "cold porridge" } } // 匹配"cold porridge"
]
}
}
]
}
}
}
}
}

这个查询语句的意图为:匹配包含 "my favorite food" 紧接着 "hot water""cold porridge" 的文档。下面是几个是否可以匹配的例子:

  • 该查询语句可以匹配my favorite food is cold porridge
  • 但是无法匹配 it's cold my favorite food is porridge
  • 无法匹配:my food favorite is cold porridge;如果第一个子间隔的"ordered" : false 的话,就可以匹配;
  • 无法匹配:my food is cold porridge,但是,如果第一个子间隔的"max_gaps" : 1 的话,就可以匹配;

Intervals查询的核心是定义一组规则对象,用于匹配文档中的词条,Intervals查询子句顶级参数为field,表示搜索的字段名,field内支持的规则如下:

规则 描述
match 精确匹配指定的词条或短语
prefix 匹配以指定前缀开头的词条
wildcard 匹配包含通配符模式的词条
fuzzy 匹配与指定词条的编辑距离在指定范围内的词条
all_of 所有子规则必须满足
any_of 至少有一个子规则满足即可

通过灵活组合这些规则,可以构建出复杂的匹配模式,以满足对词条位置、顺序、间隔等方面的精确要求。例如一个规则可以要求“词条A必须紧跟着词条B,且它们之间的间隔不超过3个词条”。另一个规则可以是“匹配包含’lucene’前缀且编辑距离在2以内的任意词条”。

每个规则支持的参数可以详细参考官方手册,这里就不详细说明了;

Match query

match是全文本查询的标准查询,包括模糊匹配、短语或邻近查询。通过匹配提供的textnumberdate,或者bool值返回对应的doc,输入的text在进行匹配前,会执行和doc中对应field的text相同的分词处理。

match查询的语法结构顶级参数为:field,表示要搜索的doc的字段名,field参数内部支持的顶级参数列表如下:

参数 描述
query (必选)您希望在提供的<field>中找到的文本、数字、布尔值或日期。match查询会在执行搜索之前分析任何提供的文本。这意味着match查询可以在text字段中搜索分析后的词元,而不是精确词条。
analyzer (可选,字符串)用于将query值中的文本转换为词元的分析器。默认为映射到<field>的索引时分析器。如果未映射任何分析器,则使用索引的默认分析器。
auto_generate_synonyms_phrase_query (可选,布尔值)如果为true,则会自动为多词条同义词创建短语查询。默认为true
boost (可选,浮点数)用于减少或增加查询的相关性分数的浮点数。默认为1.0
fuzziness (可选,字符串)模糊查询,允许的最大编辑距离。
max_expansions (可选,整数)查询将扩展到的最大词条数。默认为50
prefix_length (可选,整数)模糊匹配时保留不变的起始字符数。默认为0
fuzzy_transpositions (可选,布尔值)如果为true,则模糊匹配的编辑包括两个相邻字符的转置(ab → ba)。默认为true
fuzzy_rewrite (可选,字符串)用于重写查询的方法。有关有效值和更多信息,请参阅rewrite参数。
lenient (可选,布尔值)如果为true,则忽略格式错误,例如为数字字段提供文本query值。默认为false
operator (可选,字符串)用于解释query值中文本的布尔逻辑。有效值为OR(默认)或AND
minimum_should_match (可选,字符串)文档必须匹配的最小子句数,才能返回。有关有效值和更多信息,请参阅minimum_should_match参数。
zero_terms_query (可选,字符串)如果analyzer删除所有词元(例如使用stop过滤器时),是否返回任何文档。有效值为none(默认)或all

query短查询

match查询field参数的子参数query是必须的,它是全文本查询的标准查询,为了降低复杂度,ES支持简化的短查询语法格式:可以将field参数和query参数合并一起只使用field参数。如下查询description字段包含guy单词的文档,两个写法是对等的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET my-index-000001/_search
{
"query": {
"match": {
"description": {"query": "guy"}
}
}
}

GET my-index-000001/_search
{
"query": {
"match": {
"description": "guy"
}
}
}

operator参数

match查询是如何执行查询操作呢?其实它是一个bool类型的查询,match查询会对输入的text进行分析处理:包括分词,格式化等,然后通过operator参数来对text分析后的每个单词的查询执行bool操作,只有bool操作为真的时候,文档才被认为匹配。

operator参数有两个值:or(默认值)和and,如下:只要description字段包含任意单词beautifyguy,对应的doc就被认为是匹配的,

1
2
3
4
5
6
7
8
GET my-index-000001/_search
{
"query": {
"match": {
"description": "beautify guy"
}
}
}

下面的match查询要去description字段必须同时包含单词beautifyguy,对应的doc才会被认为是匹配的:

1
2
3
4
5
6
7
8
9
10
11
GET my-index-000001/_search
{
"query": {
"match": {
"description": {
"query": "beautify guy",
"operator": "and"
}
}
}
}

fuzziness模糊查询

match查询中的fuzziness参数,允许基于被查询字段的类型执行基于相似度的模糊匹配。可以通过设置prefix_lengthmax_expansions来控制模糊过程。

下面是fuzziness参数的值:

描述
0 禁用模糊匹配,match匹配默认关闭。
1, 2 指定允许的最大Levenshtein编辑距离(或编辑次数)。
AUTO 根据词条长度生成编辑距离。可选择指定低和高距离参数:AUTO:[low],[high]。如果未指定,默认值为3和6,相当于AUTO:3,6,对应以下情况:
- 长度0..2: 必须完全匹配
- 长度3..5: 允许1次编辑
- 长度>5: 允许2次编辑

如下测试:查询description字段和"guyy"相似的字段:

1
2
3
4
5
6
7
8
9
10
11
GET my-index-000001/_search
{
"query": {
"match": {
"description": {
"query": "guyy",
"fuzziness": "AUTO"
}
}
}
}

Elasticsearch中的模糊查询使用Levenshtein编辑距离来计算字符串之间的相似度。Levenshtein编辑距离是指将一个字符串转换成另一个字符串所需的最小编辑操作的次数,编辑操作包括:

  • 替换一个字符;
  • 插入一个字符;
  • 删除一个字符

例如:将"book"转换为"brook"只需一次插入操作,编辑距离为1。将"book"转换为"back"需要一次替换操作,编辑距离也为1。

Match boolean prefix query

全文本查询支持match_bool_prefix前缀匹配查询,match_bool_prefix的执行逻辑是对输入的text进行分析,然后将每个term(除了text中的最后一个单词)构建出一个 bool querytext中的最后一个单词用于prefix匹配

全文本查询的match_bool_prefix前缀匹配查询支持的参数列表和前面match查询一致,只是不同的参数针对text中的最后一个单词是否生效,如下:

  1. minimum_should_matchoperator 参数,这两个参数的作用与 match 查询中的一样,应用于构造的 bool 查询上。
  2. fuzzinessprefix_lengthmax_expansionsfuzzy_transpositionsfuzzy_rewrite 这些参数可应用于除最后一个词项之外的所有词项构造的 term 子查询上。

下面看一下示例:

1
2
3
4
5
6
7
8
GET /_search
{
"query": {
"match_bool_prefix": {
"description": {"query": "a handsome gu"}
}
}
}

等价于:

1
2
3
4
5
6
7
8
9
10
11
12
GET /_search
{
"query": {
"bool" : {
"should": [
{ "term": { "description": "a" }},
{ "term": { "description": "handsome" }},
{ "prefix": { "message": "gu"}}
]
}
}
}

Match phrase query

全文本查询支持match_phrase短语搜索的查询类型,针对输入的查询短语,会进行如下处理:

  1. 通过分析器进行分词处理,每个分词及其在原始字符串中的位置会被记录;
  2. 每个分词都会构建一个term查询,匹配的文档必须要严格有序的包含所有的词项

如下就是搜索包含"handsome guy"短语的文档:

1
2
3
4
5
6
GET /_search
{
"query": {
"match_phrase": {"description": "handsome guy"}
}
}

其实这个短语查询和全文本查询中的另一类Intervals查询比较重合,intervals查询可以控制更细的力度。

match_phrase短语搜索支持slop参数,参数值解释如下:

  • 0:这个默认值,当短语查询匹配词语时,它们必须按照查询中给定的顺序依次出现,而且它们之间不能有任何其他词语插入。
  • 2:这是一个特殊值,如果短语颠倒了也就是所谓的 “transposed”(转置),那么这种情况下的 slop 为 2。所以 slop 为 2时,表示允许搜索的短语颠倒,或者短语之间允许有两个词语的距离。

Match phrase prefix query

全文本查询支持match_phrase_prefix短语前缀搜索的查询类型,是在match_phrase短语搜索的查询类型的基础上扩展而来,支持最后一个term的前缀匹配查询

match_phrase_prefix查询的语法结构和其他全文搜索的一样,顶级参数为:field,表示要搜索的doc的字段名,field参数内部支持的顶级参数列表如下:

参数 说明
query (必需,string)要在指定字段中搜索的文本
analyzer (可选,string)用于将查询文本分词的分词器,默认使用字段映射的分词器
max_expansions (可选,整数)最后一个查询词项要扩展匹配的最大词条数,默认为50
slop (可选,整数)匹配词项间允许移动的最大位置距离,默认为0(紧邻),转置词的slop为2
zero_terms_query (可选,string)当分词器过滤掉所有词项时的处理方式,可取值none(默认,不返回文档)或all(返回所有文档,等同match_all查询)

match_phrase短语搜索的语法结构也是一样,按道理应该在match_phrase短语搜索中介绍语法结构。

Combined fields查询

全文本查询支持combined_fields组合字段查询,之前介绍的查询都是针对doc的单个字段,ES也提供了多字段组合的查询,就像这些字段的内容被索引到一个组合字段中一样。combined_fields查询过程是针对输入的字符串进行分词,然后每个分词都在所有给出的field中进行搜索。

组合字段查询针对匹配需要跨越多个text字段特别有用,例如查询某段文字是否出现在一个doc的标题,摘要或者正文中。组合字段查询针对多个text字段,只要有一个字段符合匹配条件,就认为该doc是匹配的

如下测试语句是搜索doc中的"description""name"字段,查看是否能够match匹配"handsome person"

1
2
3
4
5
6
7
8
9
GET /_search
{
"query": {
"combined_fields": {
"query": "handsome person",
"fields": ["description", "name"]
}
}
}

combined_fields查询针对查询的文本也支持operator参数,默认为”or”,如下是搜索doc中的"description""name"字段,能够match匹配"handsome person"中的所有单词。

combined_fields查询支持的参数列表如下:

参数 说明
fields (必需,字符串数组)要搜索的字段列表,支持通配符模式。只有text类型的字段受支持,且必须使用相同的analyzer分析器
query (必需,字符串)在指定字段中搜索的文本
auto_generate_synonyms_phrase_query (可选,布尔值)如果为true,将自动为多词条同义词创建短语查询,默认为true
operator (可选,字符串)解释查询文本的布尔逻辑,可取值or(默认)或and
minimum_should_match (可选,字符串)文档被返回所需的最小匹配子句数,参考minimum_should_match参数的有效值和更多信息
zero_terms_query (可选,字符串)当分词器过滤掉所有词项时的处理方式,可取值none(默认,不返回文档)或all(返回所有文档,等同match_all查询)

combined_fields查询支持指定字段boost权重,如下,title字段设置了boost=2,那么如果每个出现在title字段中的词条,会被认为出现了两次,最终提高其相关性分数。

1
2
3
4
5
6
7
8
9
GET /_search
{
"query": {
"combined_fields" : {
"query" : "distributed consensus",
"fields" : [ "title^2", "body" ]
}
}
}

Multi-match query

全文搜索支持multi_match多字段匹配,在多个字段上执行相同的常规match查询,并按照指定的方式合并每个字段的分数。

multi_match匹配的格式如下,和combined_fields组合字段查询使用很相似:

1
2
3
4
5
6
7
8
GET /_search
{
"query": {
"multi_match" : {
"query": "this is a test",
"fields": [ "subject", "message" ] }
}
}

multi_match匹配同时支持字段名的通配符匹配和字段名的权重调整

multi_match匹配支持type参数,用于表示不同的查询类型,type参数值包括如下:

类型 描述
best_fields (默认) 查找匹配任意字段的文档,但使用得分最高的单个字段的 _score。详见 best_fields
most_fields 查找匹配任意字段的文档,并将每个字段的 _score 合并。详见 most_fields
cross_fields 将使用相同分析器的字段视为一个大字段。在任意字段中查找每个查询词条。详见 cross_fields
phrase 在每个字段上执行 match_phrase 查询,并使用得分最高的单个字段的 _score。详见 phrasephrase_prefix
phrase_prefix 在每个字段上执行 match_phrase_prefix 查询,并使用得分最高的单个字段的 _score。详见 phrasephrase_prefix
bool_prefix 在每个字段上创建 match_bool_prefix 查询,并将每个字段的 _score 合并。详见 bool_prefix

这里我们会发现combined_fields组合字段查询和持multi_match多字段匹配有比较相似的地方,这里主要的差异如下:

方面 combined_fields multi_match
评分模型 视为组合字段,跨字段合并统计数据并计算综合相关性分数,尝试遵循 BM25F 模型 根据 type 使用最佳字段分数(best_fields)或合并所有字段分数(most_fields等)
查询执行方式 分析查询文本为词条序列,在每个字段上查找 也会先分析查询文本,但接着会根据 type 构建不同的字段级查询,如 match_phrase
查询类型的灵活性 只能执行 term/phrase 查询的组合 可通过 type 指定各种查询类型,如 boolphrasephrase_prefix
性能差异 只涉及分词和 term/phrase 查询,性能开销较小 需构建和执行多个复杂的字段查询,再合并分数,性能开销较大

总的来说,combined_fields查询更侧重于跨字段的单词/短语匹配,而multi_match查询则专注于灵活的跨字段查询类型组合。在相关性和性能之间做出权衡选择。如果需求简单,combined_fields可能是更好的选择

Query string query

Term-level查询

Term-level queries词条级查询,Term级别查询旨在针对结构化数据进行精确值匹配,例如日期范围、IP地址、价格或产品ID等。这类数据通常是经过某种方式编码或格式化的,要求查询时完全匹配存储在字段中的原始值。

Full-text queries全文搜索的很大区别,它们不会分词,而是将搜索词直接与字段值进行比较,能够精确无误地查找到匹配项。对于keyword字段,查询词也会根据normalizer进行归一化以保持一致性。

基于Term的查询支持类型如下:

查询类型 描述
exists query 返回包含字段的任何索引值的文档。
fuzzy query 返回包含与搜索词类似的术语的文档。Elasticsearch使用Levenshtein编辑距离来衡量相似性或模糊性。
ids query 基于它们的文档ID返回文档。
prefix query 返回在提供的字段中包含特定前缀的文档。
range query 返回包含在提供的范围内的术语的文档。
regexp query 返回包含与正则表达式匹配的术语的文档。
term query 返回在提供的字段中包含精确术语的文档。
terms query 返回在提供的字段中包含一个或多个精确术语的文档。
terms_set query 返回在提供的字段中包含最少数量的精确术语的文档。您可以使用字段或脚本定义匹配术语的最小数量。
wildcard query 返回包含与通配符模式匹配的术语的文档。

Exists query

基于termexists查询用于匹配存在指定字段名的所有doc,注意这里是匹配字段名,而不是字段的值,如下:用于搜索所有包含"name"字段的doc:

1
2
3
4
5
6
GET /_search
{
"query": {
"exists": {"field": "name"}
}
}

常见的一些使用exists查询的典型场景包括:

  • 检查某个字段是否为null或缺失;
  • 将有某个字段的文档与没有的文档分开;
  • 对于某些能区分有无的业务场景;快速检索出存在值的文档

如下搜索就是通过 boolean query 中的must_not结合基于Term的exists query用来发现哪些doc缺失了"user.id"字段

1
2
3
4
5
6
7
8
9
10
11
12
GET /_search
{
"query": {
"bool": {
"must_not": {
"exists": {
"field": "user.id"
}
}
}
}
}

Fuzzy query

基于termfuzzy查询用于返回和搜索词相近的doc,和全文检索中的match查询中的fuzziness参数一样,使用Levenshtein编辑距离来衡量相似性或模糊性。这里在前面[fuzziness模糊查询]已经介绍了Levenshtein距离的概念,这里不再赘述了。

如下测试用例,匹配"user.id"的值和"ki"相似的文档:

1
2
3
4
5
6
7
8
GET /_search
{
"query": {
"fuzzy": {
"user.id": {"value": "ki"}
}
}
}

基于termfuzzy查询的语法结构,"fuzzy"参数后的顶级参数为字段名,字段名的顶级参数列表如下:

参数 描述
value (必需,字符串) 要在指定字段中查找的词条值
fuzziness (可选,字符串) 允许的最大编辑距离,用于模糊匹配,详见 Fuzziness 的有效值和更多信息
max_expansions (可选,整数) 创建的最大变体数量,默认为 50
prefix_length (可选,整数) 在创建变体时保持不变的起始字符数,默认为 0
transpositions (可选,布尔) 是否包括相邻两个字符转置的编辑,如 ab 到 ba,默认为 true
rewrite (可选,字符串) 重写查询的方法,有效值和更多信息见 rewrite 参数

基于termfuzzy查询和全文检索中的match查询中的fuzziness查询有什么区别呢?

  • 搜索方式不同term 查询的 fuzzy 直接将查询词条与字段词条值进行比对。match 查询的 fuzziness 先对查询语句分词,再对每个词条分别查找近似词条进行匹配。
  • 适用场景不同term 查询的 fuzzy 适用于已知存在的结构化数据,如ID、代码等关键词进行模糊匹配。match 查询的 fuzziness 更多应用于全文本场景下的拼写错误容错。

IDs query

基于termIDs查询用于搜索指定_id字段的doc,比较简单,如下:

1
2
3
4
5
6
GET /_search
{
"query": {
"ids" : {"values" : ["1", "4", "100"]}
}
}

Prefix query

基于termprefix前缀查询和全文本查询的match_bool_prefix前缀匹配比较类似,但是区别还是比较大的,就像前面分析的fuzzy查询一样,主要是搜索方式和适用场景的不同,这里不再赘述。

如下是基于termprefix前缀查询的示例,查询"user.id"字段以"ki"前缀开始的document:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /_search
{
"query": {
"prefix": {
"user.id": {"value": "ki"}
}
}
}

// 和match查询一样,也可以用短查询的语法格式
GET /_search
{
"query": {
"prefix": {"user.id": "ki"}
}
}

基于termprefix前缀查询的语法格式,顶级参数是field的名字,field内的参数列表如下:

参数 描述
value (必需,字符串) 要在指定字段中查找的词条值的开头字符
rewrite (可选,字符串) 用于重写查询的方法,有效值和更多信息见 rewrite 参数
case_insensitive (可选,布尔值) 从7.10.0版本开始,当设置为true时,允许以ASCII不区分大小写的方式匹配值与索引字段值。默认为false,区分大小写取决于字段映射的设置。

Range query

基于termrange查询是针对结构化数据比较常见的一种查询方式。我们先看一个例子:

1
2
3
4
5
6
7
8
GET /_search
{
"query": {
"range": {
"age": {"gte": 10, "lte": 20, "boost": 2.0}
}
}
}

上面的例子是要所有doc中的age字段执行范围查询,所有字段值在[10, 20]范围的doc的相关性分数乘以2的权重系数。

range查询的语法结构如下:在field字段名顶级参数下面的参数包括:

参数 描述
gt (可选) 大于指定值
gte (可选) 大于等于指定值
lt (可选) 小于指定值
lte (可选) 小于等于指定值
format (可选,字符串) 用于转换查询中日期值的格式,覆盖字段映射中的格式设置
relation (可选,字符串) 指定范围查询如何匹配范围字段值,取值为INTERSECTS(默认,相交)、CONTAINS(包含)或WITHIN(被包含)
time_zone (可选,字符串) 用于将查询中的日期值转换为UTC的时区或UTC偏移量,如+01:00America/Los_Angeles
boost (可选,浮点数) 用于降低或提高查询相关性评分的系数,默认为1.0

当要查询field类型参数是date字段类型,我们可以使用 date math 在这四个参数中:gtgteltlte,如下查询是搜索timestamp字段的日期在今天和昨天之间:

1
2
3
4
5
6
7
8
9
10
11
GET /_search
{
"query": {
"range": {
"timestamp": {
"gte": "now-1d/d",
"lte": "now/d"
}
}
}
}

Term query

基于term级别的term查询用于精确的匹配字段值

term查询的语法结构为,term参数下的顶级参数为field名字,顶级参数为field下的参数列表如下:

参数 含义
value(值) 在提供的字段中查找的项。必须与字段值完全匹配,包括空格和大小写。
boost(增强) 用于调整查询的相关性得分的浮点数。默认值为1.0。可以增加或减少相关性得分。
case_insensitive 允许在索引字段值时对ASCII进行不区分大小写匹配。默认为false。

需要注意的是:避免使用term查询来搜索text字段,因为ES默认会对text字段进行分词处理,如果用term查询,不会对输入进行分析处理,直接用输入整体和text字段的分析后的每个分词进行匹配这可能和预期的结果不一致,且效率较低。

如下:

1
2
3
4
PUT my-index-000001/_doc/1
{
"full_text": "Quick Brown Foxes!"
}

如果使用如下term查询,结果会是匹配不到的:

1
2
3
4
5
6
7
8
GET my-index-000001/_search
{
"query": {
"term": {
"full_text": "Quick Brown Foxes!"
}
}
}

参考Multi-fieldsDynamic mapping后知道,默认ES会针对JSON string数据类型同时映射为text主字段和keyword子字段,所以如果想要term查询返回结果,可以使用默认创建的"keyword"子字段进行搜索:

1
2
3
4
5
6
7
8
GET my-index-000001/_search
{
"query": {
"term": {
"full_text.keyword": "Quick Brown Foxes!"
}
}
}

或者改为通过match查询来进行匹配,如下:

1
2
3
4
5
6
7
8
GET my-index-000001/_search
{
"query": {
"match": {
"full_text": "Quick Brown Foxes!"
}
}
}

如果使用term查询来搜索text字段,可能和预期的结果不一致的还有一个点。ES默认会对text字段进行分析处理,包括:分词,格式化等,所以如下term搜索也是无法匹配的:

1
2
3
4
5
6
GET my-index-000001/_search
{
"query": {
"term": {"full_text": "Quick"}
}
}

原因就是text字段在分词后的各个单词会被格式化,包括统一转换为小写。所以上面的term查询需要修改如下才能够匹配:

1
2
3
4
5
6
7
8
9
10
11
GET my-index-000001/_search
{
"query": {
"term": {
"full_text": {
"value": "Quick",
"case_insensitive": true
}
}
}
}

Terms query

基于term级别的terms查询用于精确的匹配多个字段值terms查询和term查询很类似,除了可以支持搜索多个值,只有doc中的该字段精确匹配任意一个查询的值,该doc就被认为命中。

如下示例,搜索doc中"user.id"字段值是"kimchy",或者 "elkbee"

1
2
3
4
5
6
7
8
9
GET /_search
{
"query": {
"terms": {
"user.id": [ "kimchy", "elkbee" ],
"boost": 1.0
}
}
}

terms 查询的顶级参数列表如下:

参数 描述
<field> (可选,对象) 要搜索的字段。该参数的值是一个词条值数组,用于在指定字段中查找精确匹配项。要使文档被返回,至少一个词条值必须与字段值完全匹配,包括空格和大小写。
boost (可选,浮点数) 用于降低或提高查询相关性评分的系数,默认为 1.0。

terms 查询有一个很重要的特性是Terms lookup,就是允许从另一个字段、索引或者外部源动态获取要匹配的词条值列表来进行搜索。针对terms loopup查询的语法结构如下:

参数 描述
index (必需,字符串) 从中获取字段值的索引名称。
id (必需,字符串) 从中获取字段值的文档ID。
path (必需,字符串) 要从中获取字段值的字段名称。Elasticsearch将使用这些值作为查询的搜索词条。如果字段值包括嵌套内部对象数组,可以使用点符号语法访问这些对象。
routing (可选,字符串) 从中获取词条值的文档的自定义路由值。如果在索引文档时提供了自定义路由值,则需要此参数。

如下示例,表示"my-index-000002"中的id为2的document中读取"color"字段的值,然后在"my-index-000001"中进行terms 精确匹配

1
2
3
4
5
6
7
8
9
10
11
12
GET my-index-000001/_search?pretty
{
"query": {
"terms": {
"color" : {
"index" : "my-index-000002",
"id" : "2",
"path" : "color"
}
}
}
}

Terms set query

基于term级别的terms_set查询用于精确的匹配多个字段值,且可以指定最小匹配值的个数

terms_set查询的语法结构为,terms_set参数下的顶级参数为field名字,顶级参数为field下的参数列表如下:

参数 描述
terms (必需,字符串数组) 词条数组。要使文档被返回,必须有指定数量的词条与字段值完全匹配,包括空格和大小写。所需的匹配词条数量由 minimum_should_match_fieldminimum_should_match_script 参数定义。
minimum_should_match_field (可选,字符串) 包含要求匹配的词条数量的数值型字段,注意这里参数值,是另一个数值字段名
minimum_should_match_script (可选,字符串) 包含要求匹配的词条数量的自定义脚本。脚本的参数和有效值,请参阅 Scripting。

如下示例:

1
2
3
4
5
6
7
8
9
10
11
GET /job-candidates/_search
{
"query": {
"terms_set": {
"programming_languages": {
"terms": [ "c++", "java", "php" ],
"minimum_should_match_field": "required_matches" // 该字段名字需要在对应的doc中事先定义
}
}
}
}