Elasticsearch-搜索DSL

Elasticsearch 搜索DSL


Query DSL

Elasticsearch Guide [7.17] » Query DSL https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl.html

DSL(Domain Specific Language) 领域特定语言,Elasticsearch 里描述检索条件的一种语言,格式为 JSON,包括两种类型的语句:

  • Leaf query clauses 叶子查询,叶子查询匹配具体字段的值,例如 match, term, range,叶子查询可单独使用。
  • Compound query clauses 复合查询,以逻辑方式组合多个叶子、复合查询为一个查询,例如 bool

term 精确词查询 match 匹配查询 multi_match 多条件查询


查询(Query)和过滤(Filter)上下文

Query and filter context https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html

默认情况下,es 会根据相关性对每个匹配结果进行排序,相关性描述每个文档和查询条件的匹配度。 相关性分值是个正的浮点数,在检索结果每个文档的 _score 字段返回,这个值越大,表示此文档和查询条件越相关。 相关性分值计算取决于查询子句是在 query 上下文中还是在 filter 上下文中。

查询(Query)上下文

query 上下文中的查询语句解决 “这个文档有多匹配这个查询条件?” 的问题,所以除了决定文档是否匹配,还会计算一个相关性分值放到 _score 字段返回。 查询条件中有 query 参数时,就是 query 上下文。

过滤(Filter)上下文

filter 上下文中的查询语句解决 “这个文档是否匹配这个查询条件?” 的问题,答案只有“是”或“不是”,不需要评分。filter 上下文经常用于结构化数据的过滤,例如: timestamp 是否在 2015 ~ 2016 范围内? status 是否等于 published?

频繁使用的过滤查询会被 es 自动缓存,用于提高查询性能。

查询条件中有 filter 参数时,就是 filter 上下文。例如:

  • bool 查询中的 filter 或 must_not 参数
  • constant_score 查询中的 filter 参数
  • filter 聚合查询

Query和Filter的区别

query 需要计算相关性,按照分数进行排序,而且无法cache结果 filter 不需要计算相关性,且会缓存频繁查询的 filter 结果,所以 filter 会更快

如果是用于搜索,需要将最相关的数据先返回,那么用 query 如果只是要根据一些条件筛选出一部分数据,不关注其相关性排序,那么用 filter

默认情况下,ES 通过一定的算法计算返回的每条数据与查询语句的相关度,并通过 score 字段来表征。 但对于非全文索引的使用场景,用户并不 care 查询结果与查询条件的相关度,只是想精确的查找目标数据。 此时,可以通过 query-bool-filter 组合来让 ES 不计算 score,并且尽可能的缓存 filter 的结果集,供后续包含相同 filter 的查询使用,提高查询效率。


minimum_should_match 参数

minimum_should_match 最小匹配数量。例如,如果设置为 2,那么至少有两个 term 匹配文档才会被认为是匹配的。

  • 正整数,例如3,表示最小匹配词数量
  • 负整数,例如-2,query 分词后的词数量减去2
  • 百分比,例如75%,query 分词后 75% 的 term 匹配
  • 负百分比,例如-25%,query 分词后 (1-25%) 的 term 匹配
  • 3<90%,query 分词数小于等于3则全都需要匹配,分词数大于3则匹配 90%
  • 2<-25% 9<-3,query 分词数小于等于2则全都需要匹配,分词数在3-9之间则需匹配75%,分词数大于9则总数-3个分词需要匹配。

Elastic Docs ›Elasticsearch Guide [7.17] ›Query DSL minimum_should_match parameter https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-minimum-should-match.html


复合查询(Compound Query)

Compound queries https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html

复合查询以逻辑的方式将多个叶子、复合查询组合为一个查询,可能是对查询结果和分数的合并,可能改变其行为,或者从 query 上下文切换为 filter 上下文。


Boolean 组合查询

Boolean query https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html

bool 查询将多个查询子句组合到一起,多个子查询之间的逻辑关系是与(and),返回的文档必须匹配全部子查询。 支持的查询类型有:

  • must 返回文档必须匹配 must 条件,会影响相关性得分。多个 must 查询的得分相加得到最终得分
  • should 返回文档应当匹配 should 条件,会影响相关性得分。可通过 minimum_should_match 参数指定至少匹配几个 should 条件。
  • filter 返回文档必须严格匹配 filter 条件,在 过滤上下文(Filter Context) 中执行,和 must 唯一的区别是不影响相关性得分。
  • must_not 返回文档必须不匹配 must_not 条件,也在 过滤上下文(Filter Context) 中执行,不影响相关性得分。

查询语句同时包含 must 和 should 时,返回的文档必须满足 must 条件但可以不满足 should 条件,因为 must 条件优先级高于 should,但是如果也满足 should 条件,则会提高相关性得分。 bool 查询中,每个 must 或 should 条件中的得分会相加,成为最终结果的 _score 得分

例1、

POST _search
{
  "query": {
    "bool" : {
      "must" : {
        "term" : { "user.id" : "kimchy" }
      },
      "filter": {
        "term" : { "tags" : "production" }
      },
      "must_not" : {
        "range" : {
          "age" : { "gte" : 10, "lte" : 20 }
        }
      },
      "should" : [
        { "term" : { "tags" : "env1" } },
        { "term" : { "tags" : "deployed" } }
      ],
      "minimum_should_match" : 1,
      "boost" : 1.0
    }
  }
}

例2、3种不同的 filter 条件:时间范围+颜色多选+名字单选

{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "timestamp": {
              "gte": 1645157343381,
              "lte": 1646053743440
            }
          }
        },
        {
          "terms": {
            "color": [ "青色", "黄色" ]
          }
        },
        {
          "term": { "name": "小明" }
        }
      ]
    }
  },
  "sort": {
    "timestamp": {
      "order": "desc"
    }
  },
  "from": 500,
  "size": 20
}

minimum_should_match 最小匹配个数

通常 should 子句是个数组,返回的文档必须匹配 minimum_should_match 个 should 条件。 通过 minimum_should_match 参数指定返回文档需要匹配的 should 子句的个数或百分比。 如果 bool 查询包含至少一个 should 子句且没有 must 和 filter 子句,则 minimum_should_match 默认值为 1,否则默认值为 0


adjust_pure_negative 调整纯否定查询的结果

adjust_pure_negative 参数,用于调整纯否定查询的结果 这个参数的作用是在 must、filter、should 条件都不存在,只有 must_not 条件时,如何处理查询。

  • adjust_pure_negative=true 时,如果所有查询条件都不存在,只有 must_not 条件,则 must_not 会被忽略,ES 内部会加上一个 must match_all 条件,返回所有的文档。
  • adjust_pure_negative=false 时,如果所有查询条件都不存在,只有 must_not 条件,那么 must_not 查询会被独立执行,返回不满足 must_not 条件的所有文档。

默认情况下,adjust_pure_negative 是 true,即如果没有任何其他的查询条件,must_not 查询会被忽略,返回所有的文档。


_name 命名查询

每个查询都可以有个 _name 顶层参数,可以指定查询的名字,指定 _name 后查询结果中的每个文档会有个 matched_queries 字段,指明返回的文档匹配了哪个 query

例如

{
  "query": {
    "bool": {
      "must": {
        "match_all": {
          "_name": "must_match_all"
        }
      },
      "filter": {
        "terms": {
          "_name": "filter_terms_name",
          "name": [
            "xiaoming"
          ]
        }
      }
    }
  }
}

返回结果中每个 hit 都有 matched_queries 字段指明匹配的 query

{
  "hits": {
    "total": {
      "value": 107,
      "relation": "eq"
    },
    "max_score": 1.0,
    "hits": [
      {
        "_index": "student_index",
        "_type": "_doc",
        "_id": "UsVlD5ABC1Bn9HhmZtdb",
        "_score": 1.0,
        "_source": {
          "name": "xiaoming"
        },
        "matched_queries": [
          "filter_terms_intention",
          "must_match_all"
        ]
      }
    ]
  }
}

query.bool.filter 查询的得分

只有 filter 查询的话,只在 过滤上下文(Filter Context) 中执行过滤筛选,结果的相关性得分 _score 固定是 0.0

{
  "query": {
    "bool": {
      "filter": {
        "term": {
          "status": "active"
        }
      }
    }
  }
}

filter 之外增加一个 must match_all 查询,则整个查询会在 查询上下文(Query Context) 中执行,结果的相关性得分 _score 固定是 1.0

{
  "query": {
    "bool": {
      "must": {
        "match_all": {}
      },
      "filter": {
        "term": {
          "status": "active"
        }
      }
    }
  }
}

多个 must/should 子查询得分相加

bool 查询中,每个 must 或 should 条件中的得分会相加,成为最终结果的 _score 得分 下面 must 中有两个 script_score 查询,每个里面是和一个向量字段的 cosineSimilarity 余弦相似度值(0-2),则结果得分是两者相加(0-4)

{
  "from": 0,
  "size": 12,
  "query": {
    "bool": {
      "must": [
        {
          "script_score": {
            "query": {
              "bool": {
                "adjust_pure_negative": true,
                "boost": 1.0
              }
            },
            "script": {
              "source": "cosineSimilarity(params.queryVector, 'title_vec')+1",
              "lang": "painless",
              "params": {
                "queryVector": [
                  -0.0017843246,
                  -0.015037537,
                  ...
                ]
              }
            },
            "boost": 1.0
          }
        },
        {
          "script_score": {
            "query": {
              "bool": {
                "adjust_pure_negative": true,
                "boost": 1.0
              }
            },
            "script": {
              "source": "cosineSimilarity(params.queryVector, 'content_vec')+1",
              "lang": "painless",
              "params": {
                "queryVector": [
                  -0.0017843246,
                  -0.015037537,
                  ...
                ]
              }
            },
            "boost": 1.0
          }
        }
      ],
      "filter": [
        {
          "bool": {
            "should": [
              {
                "terms": {
                  "name": [
                    "xiaoming"
                  ],
                  "boost": 1.0
                }
              }
            ],
            "adjust_pure_negative": true,
            "minimum_should_match": "1",
            "boost": 1.0
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1.0
    }
  },
  "min_score": 1.855
}

直接query 与 query.bool.must

如果只有一个条件,并且没有其他复杂的查询逻辑需要组合,那么直接写 query 和将其放入 query.bool.must 中在功能上是等价的。这两种方式都会对这个单一条件进行评估,并且返回满足该条件的文档。

直接写 query 条件:

{
  "query": {
    "match": {
      "field": "value"
    }
  }
}

将条件放入 query.bool.must 中:

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "field": "value"
          }
        }
      ]
    }
  }
}

在这两种情况下,Elasticsearch 都会查找字段 field 包含 value 的文档。虽然它们在结构上不同,但最终的结果集将是相同的。


Boosting 反向减分查询

Boosting query https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-boosting-query.html

返回匹配 positive 条件的文档,对于匹配 negative 条件的文档会降低其相关性分数。 boosting 查询不会将匹配 negative 条件的文档从结果中删除,只是降低他们的相关性得分。如果想完全排除这些文档,应该使用 bool 查询的 must_not 子句。

boosting 查询有两个必填参数:positive 和 negative,以及一个可选的 negative_boost 因子:

  • positive:这个参数定义一个查询,所有匹配这个查询的文档都会被包含在结果中。
  • negative:这个参数也定义一个查询,但是所有匹配这个查询的文档的得分都会被降低。
  • negative_boost:这个参数是一个小于1的浮点数,用于降低匹配 negative 查询的文档的得分。

如果一个文档同时满足 positive 和 negative 条件,他的得分计算方式为: 1、计算此文档在 positive 条件下的相关性得分 2、将得分乘以 negative_boost 获得最终得分

例如,所有包含 apple 的文档都会被包含在结果中。然而,如果这些文档同时也包含 pie,那么它们的得分就会被降低 50%

{
  "query": {
    "boosting": {
      "positive": {
        "term": {
          "text": "apple"
        }
      },
      "negative": {
         "term": {
           "text": "pie"
         }
      },
      "negative_boost": 0.5
    }
  }
}

constant_score 常数得分查询

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-constant-score-query.html

constant_score 查询将所有匹配的文档的得分设置为相同的常数值。 这在你想忽略文档与查询的相关性,只关心文档是否满足查询条件的场景中非常有用。

constant_score 查询接收两个参数:filter 和 boost

  • filter:这个参数定义了一个 filter 查询,所有匹配这个查询的文档都会被包含在结果中。注意 filter 查询不计算相关性得分
  • boost:这个参数是一个浮点数,所有匹配的文档的得分都会被设置为这个值,默认是 1.0

例如,所有 category 字段值为 book 的文档都会被包含在结果中,并且它们的得分都会被设置为1.2

{
  "query": {
    "constant_score": {
      "filter": {
        "term": {
          "category": "book"
        }
      },
      "boost": 1.2
    }
  }
}

dis_max 分离最大化查询

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-dis-max-query.html

分离最大化查询(Disjunction Max Query)将任何与任一查询匹配的文档作为结果返回,但 _score 评分只计算最佳匹配的评分。 dis_max 查询,只取分数最高的那个 query 的分数,但返回结果是全部 query 匹配的结果。

例如在 title 和 body 上进行 term 查询:

GET /_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "term": { "title": "Quick pets" } },
        { "term": { "body": "Quick pets" } }
      ],
      "tie_breaker": 0.7
    }
  }
}

dis_max 查询的得分 = 最佳匹配的得分 + sum(其他query匹配的得分 * tie_breaker) tie_breaker 参数,默认是 0.0,非最佳匹配 query 的分值权重,取值是 0 到 1.0 之间的小数值。


Match 全文查询

Elasticsearch Guide [7.17] » Query DSL » Full text queries https://www.elastic.co/guide/en/elasticsearch/reference/7.17/full-text-queries.html


match 分词查询

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Match query https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-match-query.html

match 查询会对入参的 text 进行分词后再和文档匹配。

例如

GET /_search
{
  "query": {
    "match": {
      "message": {
        "query": "this is a test"
      }
    }
  }
}

可以将 match 查询的 <field>query 合并简化,例如:

GET /_search
{
  "query": {
    "match": {
      "message": "this is a test"
    }
  }
}

例如,使用 match 查询 content 字段中的 java 关键词

{
    "query" : {
        "match" : {
            "content" : "java"
        }
    }
}'

结果:

{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 0.9336268,
        "hits": [
            {
                "_index": "article",
                "_type": "_doc",
                "_id": "u__ojXEBrN9oq5tgGcul",
                "_score": 0.9336268,
                "_source": {
                    "title": "java基础",
                    "pathname": "/article/java-basic",
                    "content": "java基础介绍"
                }
            }
        ]
    }
}

match 查询的参数

query 输入的查询文本

analyzer 对查询语句进行分词的分词器,默认是对应字段的分词器。

operator 对query进行分词后,多个 term 之间的关系,默认 OR,可选 AND

  • OR 只要文档中包含任何一个查询词,就会被认为是匹配的
  • AND 那么只有文档中包含所有查询词,才会被认为是匹配的

prefix_length 用于模糊查询。它指定了一个词的前缀长度,只有这个长度之后的字符才会被模糊匹配。默认0。

max_expansions 用于模糊查询和前缀查询。它指定了最大的扩展词数量,也就是说,查询词可以被扩展成多少个近义词。默认50。

fuzzy_transpositions 用于模糊查询。如果设置为 true,那么查询词中的字符可以被交换位置。例如,如果查询词是 “abcd”,那么 “abdc” 也会被认为是匹配的。默认true。

fuzziness 这个参数用于模糊查询。它指定了允许的最大编辑距离,也就是说,查询词和文档中的词之间可以有多少个字符的差异。

lenient 如果设置为 true,那么如果字段不存在或者查询词格式错误,Elasticsearch 会忽略这个错误,而不是抛出异常。默认false。

zero_terms_query 当查询词被分析器全部过滤掉时,查询的行为。如果设置为 “NONE”,那么查询不会匹配任何文档。如果设置为 “ALL”,那么查询会匹配所有文档。默认 NONE。

auto_generate_synonyms_phrase_query 如果设置为 true,那么查询词会被当作短语查询,并且会自动生成同义词。默认true

boost 用于调整查询的权重,数值越大,权重越高,匹配的文档排名越靠前。

minimum_should_match 最小匹配数量。例如,如果设置为 2,那么至少有两个 term 匹配文档才会被认为是匹配的。

默认 match 查询参数为:

{
    "query": {
        "match": {
            "content": {
              "query": "查询文本",
              "operator": "OR",
              "minimum_should_match": "2",
              "prefix_length": 0,
              "max_expansions": 50,
              "fuzzy_transpositions": true,
              "lenient": false,
              "zero_terms_query": "NONE",
              "auto_generate_synonyms_phrase_query": true,
              "boost": 1.0
            }
        }
    },
    "from": 0,
    "size": 10
}

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-minimum-should-match.html


match_phrase 短语查询(词序列匹配)

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Match phrase query https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-match-query-phrase.html

Match phrase 查询会先对入参文本进行分词,之后匹配包含全部分词的文档,并且顺序要和入参一致。 Match phrase 用于查找包含精确词序列的文档

注意:Match phrase 也会先对入参分词,并不是有的博客中说的 Match phrase 查询不对入参分词。

slop 参数告诉 match_phrase 分词相隔多远时仍然能将文档视为匹配,默认值是 0。 slop 参数用于放宽对词序的严格要求。Slop 指定了词条之间可以“移动”的次数,以便仍然被认为是匹配的。例如,slop 为 1 允许查询中的词条交换位置一次。 例如入参 I like riding 想匹配文档 I like swimming and riding,需要将 riding 向前移动两个位置,所以 slop 设为 2 时可匹配到。

{
  "query": {
    "match_phrase": {
      "message": {
        "query": "I like riding",
        "slop": 2
      }
    }
  }
}

match_phrase_prefix 短语前缀查询

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Match phrase prefix query https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-match-query-phrase-prefix.html

和 Match phrase 查询类似,Match phrase prefix 查询会先对入参文本进行分词,之后匹配包含全部分词的文档,并且顺序要和入参一致,只不过最后一个单词会当做前缀进行匹配。

例如入参 "quick brown f" 可匹配 "quick brown fox" 或 "two quick brown ferrets",但不会匹配 "the fox is quick and brown"

GET /_search
{
  "query": {
    "match_phrase_prefix": {
      "message": {
        "query": "quick brown f"
      }
    }
  }
}

intervals 词间隔查询

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-intervals-query.html

intervals 查询提供了一种在文本字段中查找满足特定条件的词组的方式。这些条件可以包括词组中词项的顺序,词项之间的最大距离,以及对词项本身的匹配条件。 相比于 match_phrase 查询,intervals 查询提供了更精细的词语间隔、顺序控制。例如,你可以为每个词项指定不同的最大间隔,你还可以嵌套查询以匹配更复杂的模式。

intervals 查询的顶级参数是一个 <field> 字段名,表示想在哪个字段上进行搜索。 参数的值是一个规则对象,可选的匹配规则有:

  • match 匹配分词文本
  • prefix 匹配前缀
  • wildcard 匹配通配符表达式
  • fuzzy 模糊匹配
  • all_of 内嵌多个其他规则,且所有规则都必须匹配。
  • any_of 内嵌多个其他规则,任意规则匹配即可

match 规则的参数有:

  • query 必填,要查询的文本
  • max_gaps 可选,单词之间的最大间隔,超过最大间隔被认为是不匹配。默认值-1表示不限制间隔,设为0表示 term 之间必须相邻挨着。
  • ordered 可选,是否需要文本中的单词按照给定的顺序出现。默认为 false,表示单词可以以任意顺序出现。
  • filter 一个可选的过滤条件对象,可以是before、after 或 containing 等,可以用于进一步限制匹配的词组。例如,你可以指定匹配的词组必须出现在其他特定词组之前、之后或者包含其他特定词组。
  • analyzer 可以指定 query 使用的分析器,默认使用顶级 <field> 字段的分析器。
  • use_field 可选,可指定一个字段名,之后间隔匹配将在此字段上进行,而不是顶级 <field> 字段,默认分析器也会使用此字段的分析器。

all_of 规则的参数有:

  • intervals 必填,一个数组,定义了一组间隔查询条件,所有这些条件都必须匹配。
  • max_gaps 可选,这组查询条件之间的最大间隔。默认值-1表示不限制间隔宽度,设为0表示 term 之间必须相邻挨着。
  • ordered 可选,是否有序,是否必须按照定义的顺序匹配这些查询条件。默认false,如果设为true,intervals 中的规则条件必须按照定义顺序匹配。
  • filter 一个可选的过滤条件对象,可以是before、after 或 containing 等

filter 过滤器参数:

  • before 指定一个 query 对象,intervals 查询结果必须出现在 before 条件之前才算匹配
  • after 指定一个 query 对象,intervals 查询结果必须出现在 after 条件之后才算匹配
  • containing 指定一个 query 对象,intervals 查询结果必须包含 containing 条件才算匹配
  • not_containing 指定一个 query 对象,intervals 查询结果必须不包含 not_containing 条件才算匹配
  • contained_by 指定一个 query 对象,intervals 查询结果必须被 contained_by 条件包含才算匹配
  • not_contained_by 指定一个 query 对象,intervals 查询结果必须不被 not_contained_by 条件包含才算匹配
  • overlapping 指定一个 query 对象,intervals 查询结果必须和 overlapping 条件重叠才算匹配
  • not_overlapping 指定一个 query 对象,intervals 查询结果必须不和 not_overlapping 条件重叠才算匹配
  • script 返回 boolean 值的一个脚本,intervals 查询结果经 script 脚本计算后必须返回 true 才算匹配

例1、在message字段中查找包含the quick brown词组的文档。 max_gaps参数表示单词之间的最大间隔,值为1表示词组中的单词之间最多可以有一个其他单词。 ordered参数表示词组中的单词必须按照给定的顺序出现。

{
  "query": {
    "intervals" : {
      "message" : {
        "match" : {
          "query" : "the quick brown",
          "max_gaps" : 1,
          "ordered" : true
        }
      }
    }
  }
}

例2、在message字段中查找包含"the quick brown"这个短语的文档,但这个短语必须出现在"fox"之后。

{
  "query": {
    "intervals" : {
      "message" : {
        "match" : {
          "query" : "the quick brown",
          "filter" : {
            "after" : {
              "match" : { "query" : "fox" }
            }
          }
        }
      }
    }
  }
}

例3、在 message 字段上进行匹配 all_of 内有两个match查询条件,它们都必须匹配。第一个match查询查找的是"the quick brown"这个短语,最多允许有一个词的间隔,并且单词必须按顺序出现。第二个match查询查找的是"jumps over"这个短语,最多允许有两个词的间隔,并且单词可以不按顺序出现。 这两个查询条件之间最多可以有5个词的间隔(由max_gaps参数控制),并且必须按照定义的顺序出现(由ordered参数控制)。 此外,还定义了一个filter条件,表示这两个查询条件必须出现在"the lazy dog"这个短语之后。

{
  "query": {
    "intervals" : {
      "message" : {
        "all_of" : {
          "intervals" : [
            { "match" : { "query" : "the quick brown", "max_gaps" : 1, "ordered" : true } },
            { "match" : { "query" : "jumps over", "max_gaps" : 2, "ordered" : false } }
          ],
          "max_gaps" : 5,
          "ordered" : true,
          "filter" : {
            "after" : {
              "match" : { "query" : "the lazy dog" }
            }
          }
        }
      }
    }
  }
}

combined_fields 联合字段查询(7.11+)

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-combined-fields-query.html

combined_fields 查询支持在多个字段上进行匹配,它将多个字段的值合并到一个大的字符串中,然后在这个大的字符串上执行搜索。 combined_fields 是 以词为中心的(term-centric) 查询,首先将 query 查询串分词为多个 term,对于每个term,在多个 fields 中进行匹配。

combined_fields 查询特别适合一个整体被拆分为多个子部分字段,需要在这些子部分字段上匹配的场景 例如文章可拆分为 title, abstract, body,需要在这三个字段上匹配:

GET /_search
{
  "query": {
    "combined_fields" : {
      "query":      "database systems",
      "fields":     [ "title", "abstract", "body"],
      "operator":   "and"
    }
  }
}

combined_fields 和 multi_match 的区别

1、combined_fields 查询要求 fields 中的多个字段的分析器 analyzer 必须相同,如果想在不同 analyzer 的多个字段上匹配,则 multi_match 更合适。multi_match 允许在 text 和 非text 类型字段上一起匹配,也允许 text 类型字段有不同的 analyzer

2、multi_match 的 best_fields 和 most_fields 模式是 **以字段为中心的(field-centric)的** 查询,相反,combined_fields 是 以词为中心的(term-centric) 查询,operator 和 minimum_should_match 参数都是应用到分词后的每个 term 上的。

举例来说:

GET /_search
{
  "query": {
    "combined_fields" : {
      "query":      "database systems",
      "fields":     [ "title", "abstract"],
      "operator":   "and"
    }
  }
}

的执行模式是:

+(combined("database", fields:["title" "abstract"]))
+(combined("systems", fields:["title", "abstract"]))

也就是说,每个 term(在这里是 database 和 systems)必须在 "title"、"abstract" 中的至少一个字段上出现,文档才能匹配。

3、multi_match 的 cross_fields 模式也是 以词为中心的(term-centric) 查询方式,其中的 operator 和 minimum_should_match 参数也会应用在每个 term 上,而 combined_fields 查询相比 cross_fields multi_match 查询的优势是其基于 BM25F 的更强壮和可解释的相关性算法。 combined_fields 查询中的多个 field 的相关性算法也必须相同。


multi_match 多字段查询

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-multi-match-query.html

multi_match 查询支持在多个字段中搜索单个查询字符串,当希望在两个或多个字段中的任何一个中查找包含给定查询字符串的文档时非常有用。

multi_match 如果在 fields 参数中指定的字段名不存在,​Elasticsearch 不会报错,而是会自动忽略这些不存在的字段,仅对存在的字段执行搜索

例如在 subject 或 message 中查询 "this is a test":

GET /_search
{
  "query": {
    "multi_match" : {
      "query":    "this is a test", 
      "fields": [ "subject", "message" ] 
    }
  }
}

可通过通配符指定字段名,例如:

GET /_search
{
  "query": {
    "multi_match" : {
      "query":    "Will Smith",
      "fields": [ "title", "*_name" ] 
    }
  }
}

指定每个字段的评分权重(boost)

可通过 ^ 单独设置某个字段的权重(boost),比如下面将 subject 字段的得分乘以3倍,默认每个字段的 boost 都是 1.0

GET /_search
{
  "query": {
    "multi_match" : {
      "query" : "this is a test",
      "fields" : [ "subject^3", "message" ] 
    }
  }
}

如果不指定 fields 参数,multi_match 会在索引的默认字段 index.query.default_field 上匹配,默认是 *.* 即全部字段。


multi_match 的类型

multi_match 的 type 参数可指定类型:

  • best_fields 默认,返回和 fields 中任意字段匹配的文档,但 _score 得分只用 best_fields 最佳字段计算(可通过 tie_breaker 参数调)。
  • most_fields 返回 fields 中有任意字段匹配的文档,_score 得分使用每个 filed 上的得分综合计算。
  • cross_fields 将具有相同 analyzer 分词器的字段合并看做一个大字段进行匹配。
  • phrase 在每个 fields 上执行 match_phrase 查询,_score 只计算最佳匹配的得分。
  • phrase_prefix 在每个 fields 上执行 match_phrase_prefix 查询,_score 只计算最佳匹配的得分。
  • bool_prefix 在每个 fields 上执行 match_bool_prefix 查询,_score 只计算最佳匹配的得分。

best_fields 最佳匹配(默认)

best_fields 适合要求 query 的多个词在一个字段中匹配到的情况,即多个词在同一个字段中匹配上才最有意义的 例如搜索 "brown fox",subject 中只有 brown,或 message 中只有 fox,虽然都能匹配,但可能不是想要的结果,在 subject(或 message) 中同时包含 brown 和 fox 两个字段的才是最佳匹配。

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "brown fox",
      "type":       "best_fields",
      "fields":     [ "subject", "message" ],
      "tie_breaker": 0.3
    }
  }
}

best_fields 查询对每个 field 生成一个 query 语句,组合为 dis_max 查询。 best_fields 内部会转为 dis_max 分离最大化查询,所以也可以使用 dis_max 查询的 tie_breaker 参数,上面的查询等价于:

GET /_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "match": { "subject": "brown fox" }},
        { "match": { "message": "brown fox" }}
      ],
      "tie_breaker": 0.3
    }
  }
}

和 dis_max 查询一样,默认 best_fields 只用最佳匹配的得分作为结果的 _score,不过可以通过 tie_breaker 参数调整非最佳匹配的得分权重,tie_breaker 默认是 0


most_fields 多字段匹配

most_fields 适合同一份文本在不同字段中是不同分词形式存储的情况。 例如,主字段可能包含同义词、词干和无音调符号的词。第二个字段可能包含原始词,第三个字段可能包含连续词组(shingles)。通过组合所有三个字段的得分,可以实现:通过与主字段匹配可以尽可能多的匹配到文档,然后使用第二和第三个字段将最相似的结果排序到列表开头。 例如 content 是原始文本,content.ik_max 是使用 ik_max 分词器的文本,content.ik_smart, content.jieba 分别是使用其他两个分词器处理的文本,匹配时综合考虑这几个字段上的得分。

查询

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "quick brown fox",
      "type":       "most_fields",
      "fields":     [ "title", "title.original", "title.shingles" ]
    }
  }
}

内部转为 bool 复合查询执行:

GET /_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title":          "quick brown fox" }},
        { "match": { "title.original": "quick brown fox" }},
        { "match": { "title.shingles": "quick brown fox" }}
      ]
    }
  }
}

每个 match 语句的得分加到一起,除以 match 语句的个数,就是整体的得分。


cross_fields 跨字段匹配

cross_fields 适合内容被拆分到多个字段存储的情况。 例如,名字被拆分为 first_name 和 last_name 两个字段存储,在这两个字段上进行 cross_fields 类型的 multi_match 匹配时会将 first_name 和 last_name 组合为一个字段匹配,正好能匹配上。

cross_fields 执行时先将 query 分词,然后用每个 term 在全部字段上匹配。

例如

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Will Smith",
      "type":       "cross_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator":   "and"
    }
  }
}

执行时,先用 Will 在 first_name 和 last_name 上匹配,再用 Smith 在 first_name 和 last_name 上匹配。 所以,全部 term 必须在至少一个 field 中出现,此 doc 才算匹配。

cross_field 如果遇到 fields 中有不同分词器的情况,会先将 fields 按分词器类型分组 group,然后使用最佳匹配的组来计算得分。 例如

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Jon",
      "type":       "cross_fields",
      "fields":     [
        "first", "first.edge",
        "last",  "last.edge"
      ]
    }
  }
}

first 和 last 是同一个分词器,first.edge 和 last.edge 是同一个分词器,则上面的查询会如此执行:

    blended("jon", fields: [first, last])
| (
    blended("j",   fields: [first.edge, last.edge])
    blended("jo",  fields: [first.edge, last.edge])
    blended("jon", fields: [first.edge, last.edge])
)

cross_field 也有 tie_breaker 参数可控制 _score 得分计算,默认只使用 最佳匹配group 的得分(即 tie_breaker=0.0)


field-centric 和 term-centric

field-centric 以字段为中心

multi_match 的 best_fields 和 most_fields 模式是 **以字段为中心的(field-centric)的** 查询方式,会为每个 field 字段生成一个 query 语句。operator 和 minimum_should_match 参数也是应用到 field 级别的。 例如

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Will Smith",
      "type":       "best_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator":   "and" 
    }
  }
}

执行模式为:

  (+first_name:will +first_name:smith)
| (+last_name:will  +last_name:smith)

也就是说,全部 term(will 和 smith)必须在单个 field 中出现,文档才匹配

term-centric 以词为中心

multi_match 的 cross_fields 模式 和 combined_fields 查询是 **以词为中心的(term-centric)** 查询方式,为 query 分词后的每个 term 生成一个 query 语句,operator 和 minimum_should_match 参数也会应用在每个 term 上。 例如

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Will Smith",
      "type":       "cross_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator":   "and"
    }
  }
}

执行模式为:

+(first_name:will  last_name:will)
+(first_name:smith last_name:smith)

也就是说,每个 term(在这里是 will 和 smith)必须在 "first_name"、"last_name" 中的至少一个字段上出现,文档才能匹配


query_string 查询(类Lucene语法查询)

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Query string query https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-query-string-query.html

query_string 查询会严格校验查询串的语法,语法错误时会报错。 由于 query_string 查询语法错误时会报错,不建议在给用户输入的搜索框中使用,可选方案:

  • 如果需要支持 Lucene 语法,使用 simple_query_string 查询
  • 如果不需要支持 Lucene 语法,使用 match 查询
GET /_search
{
    "query": {
        "query_string" : {
            "query" : "(new york city) OR (big apple)",
            "default_field" : "content"
        }
    }
}

simple_query_string 简化lucene查询

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Simple query string query https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-simple-query-string-query.html


Term 级查询

Term-level queries https://www.elastic.co/guide/en/elasticsearch/reference/current/term-level-queries.html

term 查询用于精确匹配一个字段中的确切值。它不会对查询字符串进行分词,而是将整个字符串作为单个词项进行搜索。 注意字段本身内容是否分词,取决于索引 mapping 中此字段的类型

假设有一个文档,其字段值为 "Quick brown fox"。这个字段值在存入 Elasticsearch 时通常会经过分词处理。标准的分词器会将 "Quick brown fox" 分解为三个词项:["Quick", "brown", "fox"]。 使用 "Quick" 做 term 查询时,可以和分词项 "Quick" 匹配,所以可以查到。 使用 "Quick brown" 做 term 查询时,因为 "Quick brown" 作为整体无法和任何分词项匹配,所以无法查询到。


term 等值匹配

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html


terms In多值匹配

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html

terms 查询返回包含入参 terms 列表中一个或多个精确匹配的文档。

例如,查询 user.id 包含 kimchy 或 elkbee 的文档,

GET /_search
{
  "query": {
    "terms": {
      "user.id": [ "kimchy", "elkbee" ],
      "boost": 1.0
    }
  }
}

注意: 1、如果字段存储了多个值(array values),其中任意一个在查询 terms 中即可匹配 例如 es 中有文档 "tags": ["tag1", "tag2", "tag3"] 查询 terms 是 {"query": {"terms": {"tag": ["tag3", "tag4"] }}} 也是可以匹配的,因为 tag3 出现在了查询 terms 列表中 也就是说,查询 {"terms": {"tag": ["tag3", "tag4"] }} 会匹配全部 tag 字段(无论tag字段存储了1个值还是多个值)包含 tag3 或 tag4 的文档。

2、在被分词的 text 字段上使用 terms 查询,将匹配任何包含 terms 中任何项的文档,而不仅仅是完全匹配。


terms 值个数超 max_terms_count 65536 报错

terms 值个数超过 index.max_terms_count 默认值 65536 会报错:

"caused_by":{"type":"illegal_argument_exception","reason":"The number of terms [120914] used in the Terms Query request has exceeded the allowed maximum of [65536]. This maximum can be set by changing the [index.max_terms_count] index level setting."}

terms_set 数组个数匹配

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-terms-set-query.html

terms_set 查询和 terms 查询类似,但是可以指定 term 的匹配个数。

terms_set 查询特别适合做多标签匹配,比如es文档中 tag 是个 keyword 数组,查询参数是 terms 数组,并且需要至少匹配 terms 中的 n 个才召回,此时适合使用 terms_set 查询

参数: minimum_should_match_field 指定一个 Numeric 类型的字段名,这个字段定义了一个文档需要匹配多少个项才能被认为是匹配的 minimum_should_match_script 脚本指定 terms 需要匹配的个数,source 中可以使用变量 params.num_terms 表示 terms 参数的个数

terms_set 查询有两种用法:

  • 在每个文档上增加一个和 keyword 数组字段搭配使用的 long 类型字段,指定此文档 keyword 数组字段需要匹配的个数,在查询中使用 minimum_should_match_field 参数指定该字段名。
  • 不用提前定义每个文档的匹配个数,只在查询时使用 minimum_should_match_script 脚本指定需要和入参 terms 匹配的个数

minimum_should_match_script

minimum_should_match_script 查询示例

Elasticsearch array property must contain given array items https://stackoverflow.com/questions/31916523/elasticsearch-array-property-must-contain-given-array-items

1、创建索引,tag 字段存储标签数组

{
  "mappings": {
    "properties": {
      "tag": {
        "type": "keyword"
      }
    }
  }
}

2、写入文档

doc1.tag : ["tag1","tag2"]
doc2.tag : ["tag2","tag3"]
doc3.tag : ["tag3","tag2","tag1"]

3、查询 (1)查询 tags 字段包含全部 ["tag1", "tag2", "tag3"] 的文档,少1个都不行,只召回 doc3

{
  "query": {
    "terms_set": {
      "tags": {
        "terms": [ "tag1", "tag2", "tag3" ],
        "minimum_should_match_script": {
          "source": "3"
        }
      }
    }
  }
}

source 中可以使用变量 params.num_terms 表示 terms 参数的个数,下面的查询中,由于 terms 数组长度为3,所以 params.num_terms=3

{
  "query": {
    "terms_set": {
      "tags": {
        "terms": [ "tag1", "tag2", "tag3" ],
        "minimum_should_match_script": {
          "source": "params.num_terms"
        }
      }
    }
  }
}

(2)tags 字段包含 ["tag1", "tag2", "tag3"] 中任意2个即可匹配,并且 返回结果中doc3相关性得分最高,排在第一位,因为doc3与terms匹配个数最多

{
  "query": {
    "terms_set": {
      "tags": {
        "terms": [ "tag1", "tag2", "tag3" ],
        "minimum_should_match_script": {
          "source": "2"
        }
      }
    }
  }
}

minimum_should_match_field

minimum_should_match_field 查询示例 1、创建索引,required_matches 指定 keyword 数组字段 programming_languages 需要匹配的个数

{
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword"
      },
      "programming_languages": {
        "type": "keyword"
      },
      "required_matches": {
        "type": "long"
      }
    }
  }
}

2、写入数据

PUT /job-candidates/_doc/1?refresh
{
  "name": "Jane Smith",
  "programming_languages": [ "c++", "java" ],
  "required_matches": 2
}

3、查询 minimum_should_match_field 指定为 required_matches,也就是根据每个文档的 required_matches 字段决定 programming_languages 需要匹配的 terms 个数

GET /job-candidates/_search
{
  "query": {
    "terms_set": {
      "programming_languages": {
        "terms": [ "c++", "java", "php" ],
        "minimum_should_match_field": "required_matches"
      }
    }
  }
}

prefix 前缀匹配

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-prefix-query.html

{
  "query": {
    "prefix": {
      "name": "张"
    }
  },
  "_source": ["_id", "name"]
}

range 范围查询

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html


exists 字段是否存在

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-exists-query.html

Elasticsearch 的 exists 查询是用于过滤包含指定字段且该字段具有有效值的文档,其核心逻辑是判断字段是否存在可被索引的非空值。

匹配条件 满足以下任一条件时,exists 查询会命中文档:

  • 字段存在且值非 null;
  • 字段存在且值非空数组;
  • 字段存在且值非 [null](null值数组)

​例1,若文档字段值为 "tag": "value"、"tag": [1, 2],则 exists 查询会匹配; 例2,若为 "tag": null、"tag": [] 或 "tag": [null],则 exists 不会匹配。

实现原理: exists 查询直接检查目标字段在倒排索引中是否存在有效词项。若字段未被索引(如值为 null 或空数组),则视为不存在 不参与评分:exists 查询属于过滤器上下文(Filter Context)​,不计算 _score,因此性能较高且可被缓存

1、存在性查询(exists) 检测字段是否存在有效值(非 null 或空数组):

GET /my_index/_search
{
  "query": {
    "exists": { "field": "status_code" }
  }
}

匹配逻辑:返回所有 status_code 字段有实际值的文档(包括空字符串 "",但不含 null)

2、缺失性查询(must_not + exists) 查询字段为 null 或缺失(无此字段)的文档:

GET /my_index/_search
{
  "query": {
    "bool": {
      "must_not": {
        "exists": { "field": "status_code" }
      }
    }
  }
}

​匹配逻辑:匹配 status_code 为 null、空数组或字段不存在的文档。

null_value 对 exists 存在性/缺失性查询的影响

设置 "null_value": "NULL" 后: 1、插入显式null值文档:"status_code": null,由于 null_value 的映射规则,实际索引的值为 "NULL"。此时:

  • exists 查询会将替换后的 "NULL" 视为有效值,因此该字段会被判定为存在,exists 能查到
  • must_not exists 查询无法匹配这些 null 值文档,must_not exists 查不到

2、插入空数组 "status_code": [],或缺失 status_code 字段的文档,此时:

  • exists 查不到这些文档
  • must_not exists 可以查到这些文档

wildcard 通配符查询

Elasticsearch Guide [7.17] » Query DSL » Term-level queries » Wildcard query https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-wildcard-query.html

* 匹配任何字符序列(包括空字符) ? 匹配任何单个字符

注意:避免使用以 *? 开头的查询串,速度会很慢

例如查询姓张的

GET /_search
{
    "query": {
        "wildcard" : { "name": "张*" }
    }
}

regexp 正则匹配


join 关联查询

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/joining-queries.html

nested 嵌套查询

Nested query https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-nested-query.html


脚本查询

Elastic Docs ›Elasticsearch Guide [8.8] ›Query DSL ›Specialized queries https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-script-query.html

脚本查询通常用在 filter 过滤上下文中,脚本过滤查询的功能也可通过 7.11+ 版本提供的 runtime fields 运行时字段 功能来实现。


特殊查询-script score 脚本评分查询

Elasticsearch Guide [7.17] » Query DSL » Specialized queries » Script score query https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-script-score-query.html

脚本评分查询(script_score) 可通过入参脚本定制文档的 相关性评分 函数。

例如下面的查询用 my-int 字段除以 10 作为每个文档的相关性得分

GET /_search
{
  "query": {
    "script_score": {
      "query": {
        "match": { "message": "elasticsearch" }
      },
      "script": {
        "source": "doc['my-int'].value / 10 "
      }
    }
  }
}

脚本评分查询的参数:

  • query (必填)查询对象,用于过滤
  • script (必填)脚本对象
  • min_score (可选)最小分数阈值,分数小于此阈值的文档不会返回。
  • boost (可选)如果指定了 boost 参数,最终的得分是脚本计算出的分值乘以 boost,默认值是 1.0

脚本对象

https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-using.html

Elasticsearch 中任何使用脚本的地方都用下面的格式来定义脚本:

"script": {
  "lang":   "...",
  "source" | "id": "...",
  "params": { ... }
}
  • lang 脚本语言,默认是 painless
  • source, id 脚本自身,可以通过 source 参数直接内联写入脚本内容,也可以通过 id 指定提前创建的脚本(通过脚本存储 API 创建脚本)
  • params 指定传入脚本的参数,参数名可自定义,和脚本中对应上即可。

例如:

GET my-index-000001/_search
{
  "script_fields": {
    "my_doubled_field": {
      "script": { 
        "source": "doc['my_field'].value * params['multiplier']", 
        "params": {
          "multiplier": 2
        }
      }
    }
  }
}

Elasticsearch 首次遇到新脚本会编译并缓存脚本,编译脚本是个比较耗时的动作,所以尽量保持 source 中的脚本内容保持不变,将变量放到 param 参数中。 比如上面将倍数 multiplier 改为参数可保证编译的脚本不变,变的只是 multiplier 的值,如果 source 直接写成 "source": "return doc['my_field'].value * 2" 每次改变倍数都会导致重新编译脚本。

创建和存储脚本

https://www.elastic.co/guide/en/elasticsearch/reference/current/create-stored-script-api.html


向量函数-cosineSimilarity 余弦相似度

向量函数都需要 O(n) 线性扫描匹配的文档,文档量较大时会很慢,建议先通过 query 对文档进行过滤。

向量字段的类型需设置为 dense_vector,其中 dims 是向量维度

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "my_dense_vector": {
        "type": "dense_vector",
        "dims": 3
      },
      "status" : {
        "type" : "keyword"
      }
    }
  }
}

插入数据:

PUT my-index-000001/_doc/1
{
  "my_dense_vector": [0.5, 10, 6],
  "status" : "published"
}

检索,用 status 字段做了筛选,用入参的 query_vector 向量和文档的 my_dense_vector 向量做相似度计算,结果再加 1.0 避免余弦负值,余弦相似度最小值 0.74,返回 top 10

GET my-index-000001/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "script_score": {
      "query" : {
        "bool" : {
          "filter" : {
            "term" : {
              "status" : "published" 
            }
          }
        }
      },
      "min_score": 0.74,
      "boost": 1.0,
      "script": {
        "source": "cosineSimilarity(params.query_vector, 'my_dense_vector') + 1.0", 
        "params": {
          "query_vector": [4, 3.4, -0.2]  
        }
      }
    }
  }
}


多向量余弦加权

入参向量 query_vector 同时和 title_vector 及 centent_vector 做余弦,分数取两者加权平均。

{
  "size": 2,
  "query": {
    "script_score": {
      "script": {
        "source": "0.5 * cosineSimilarity(params.query_vector, 'centent_vector') + 0.5 * cosineSimilarity(params.query_vector, 'title_vector')",
        "lang": "painless",
        "params": {
          "query_vector": [
            0.0072784424,
            -0.0020256042,
            -0.023422241
          ]
        }
      }
    }
  }
}

多向量余弦加权+判空

如果某些文档没有向量字段,cosineSimilarity 查询会报错,可在脚本中加入 if 条件判断文档的向量字段是否存在,避免查询报错。 入参向量 query_vector 同时和 title_vector, centent_vector, summary_vector 做余弦,有些文档可能没有 summary_vector 或 title_vector,使用 if 语句检查。

{
  "size": 3,
  "query": {
    "script_score": {
      "script": {
        "source": "if (doc['summary_vector'].size() == 0 && doc['title_vector'].size() == 0) {\n    return cosineSimilarity(params.query_vector, 'centent_vector')\n} else if (doc['summary_vector'].size() == 0) {\n    return 0.5 * cosineSimilarity(params.query_vector, 'centent_vector') +\n           0.5 * cosineSimilarity(params.query_vector, 'title_vector')\n} else {\n    return 0.33 * cosineSimilarity(params.query_vector, 'centent_vector') +\n           0.33 * cosineSimilarity(params.query_vector, 'title_vector') +\n           0.33 * cosineSimilarity(params.query_vector, 'summary_vector')\n}\n",
        "lang": "painless",
        "params": {
          "query_vector": [
            0.0072784424,
            -0.0020256042,
            -0.023422241
          ]
        }
      }
    }
  }
}

脚本 source 格式化后是:

if (doc['summary_vector'].size() == 0 && doc['title_vector'].size() == 0) {
    return cosineSimilarity(params.query_vector, 'centent_vector')
} else if (doc['summary_vector'].size() == 0) {
    return 0.5 * cosineSimilarity(params.query_vector, 'centent_vector') +
           0.5 * cosineSimilarity(params.query_vector, 'title_vector')
} else {
    return 0.33 * cosineSimilarity(params.query_vector, 'centent_vector') +
           0.33 * cosineSimilarity(params.query_vector, 'title_vector') +
           0.33 * cosineSimilarity(params.query_vector, 'summary_vector')
}

向量函数-dotProduct 点积

向量函数-l1norm L1距离(曼哈顿距离)

向量函数-l2norm L2距离(欧氏距离)


Aggregation 聚合查询

统计多个字段的字符数总和

计算索引内全部文档的 title.keyword 和 content.keyword 字符数总和

GET /your_index/_search
{
  "size": 0,
  "aggs": {
    "total_chars": {
      "sum": {
        "script": {
          "lang": "painless",
          "source": "int totalLength = 0; if (doc['title.keyword'].size() != 0) { totalLength += doc['title.keyword'].value.length(); } if (doc['content.keyword'].size() != 0) { totalLength += doc['content.keyword'].value.length(); } return totalLength;"
        }
      }
    }
  }
}

指标聚合

value_count 计数

{
    "size":0,
    "aggs": {
        "length_count": {
            "value_count": {
                "field": "length"
            }
        }
    }
}

结果:

{
    "aggregations": {
        "length_count": {
            "value": 2639
        }
    }
}

avg 平均值

{
    "size":0,
    "aggs": {
        "avg_length": {
            "avg": {
                "field": "length"
            }
        }
    }
}

结果

{
    "aggregations": {
        "avg_length": {
            "value": 1774.666161424782
        }
    }
}

sum 求和

{
    "size":0,
    "aggs": {
        "sum_length": {
            "sum": {
                "field": "length"
            }
        }
    }
}

结果:

{
    "aggregations": {
        "sum_length": {
            "value": 4683344.0
        }
    }
}

桶聚合

在 ES 中如果没有明确指定指标聚合,默认使用 Value Count 指标聚合,统计桶内文档总数。 默认情况,ES会根据 doc_count 文档总数,降序排序。

https://www.tizi365.com/archives/646.html

terms 分组聚合

{
    "size": 0,  // size=0代表不需要返回query查询结果,仅仅返回aggs统计结果
    "aggs": {
        "agg_groupby": { // 自定义的聚合名字
            "terms": {
                "field": "title.keyword",  // 聚合字段
                "size": 10,
                "order": { "_count": "desc" }
            }
        }
    }
}

结果

{
    "aggregations": {
        "agg_groupby": {  // 和请求中对应的自定义聚合名字
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 2342,
            "buckets": [    // 桶
                {
                    "key": "算法1",  // 桶的标识
                    "doc_count": 112   // 不指定metric指标,默认使用 value_count 计数
                },
                {
                    "key": "数据1",
                    "doc_count": 108
                },
                {
                    "key": "备份1",
                    "doc_count": 77
                }
            ]
        }
    }
}

histogram 直方图

根据数值间隔分组,适合绘制柱状图

{
    "size": 0,
    "aggs": {
        "length_hist": {
            "histogram": {
                "field": "length",
                "interval" : 300   // 分桶间隔
            }
        }
    }
}

结果:

{
    "aggregations": {
        "length_hist": {
            "buckets": [
                {
                    "key": 3300.0,   // 桶标识
                    "doc_count": 31  // 不指定metric指标,默认使用 value_count 计数
                },
                {
                    "key": 3600.0,
                    "doc_count": 51
                },
                {
                    "key": 3900.0,
                    "doc_count": 728
                }
            ]
        }
    }
}

date_histogram 日期直方图

用于根据时间、日期分桶的场景

calendar_interval:month代表每月、支持minute(每分钟)、hour(每小时)、day(每天)、week(每周)、year(每年)

{
    "size": 0,
    "aggs": {
        "date_hist": {
            "date_histogram": {
                "field": "date",
                "calendar_interval": "year",  // 间隔
                "format" : "yyyy"  // 返回结果中桶key的时间格式
            }
        }
    }
}

结果:

{
    "aggregations": {
        "date_hist": {
            "buckets": [
                {
                    "key_as_string": "2021",  // 桶标识,字符串类型
                    "key": 1609459200000,   // 桶标识,毫秒时间戳
                    "doc_count": 35    // 不指定metric指标,默认使用 value_count 计数
                },
                {
                    "key_as_string": "2022",
                    "key": 1640995200000,
                    "doc_count": 18
                },
                {
                    "key_as_string": "2023",
                    "key": 1672531200000,
                    "doc_count": 9
                }
            ]
        }
    }
}

range 范围聚合

{
    "size": 0,
    "aggs": {
        "length_range": {
            "range": {
                "field": "length",
                "ranges" : [
                    {"to": 1000},  // <= 1000 的在一个桶
                    {"from": 1000, "to": 3000},  // 1000 到 3000 的在一个桶
                    {"from": 3000} // 3000+ 的在一个桶
                ]
            }
        }
    }
}

结果:

{
    "aggregations": {
        "length_range": {
            "buckets": [
                {
                    "key": "*-1000.0",   // 桶标识
                    "to": 1000.0,
                    "doc_count": 1313   // 不指定metric指标,默认使用 value_count 计数
                },
                {
                    "key": "1000.0-3000.0",
                    "from": 1000.0,
                    "to": 3000.0,
                    "doc_count": 481
                },
                {
                    "key": "3000.0-*",
                    "from": 3000.0,
                    "doc_count": 845
                }
            ]
        }
    }
}

为了方便解析返回结果,可以通过 key 指定每个桶的标识

{
    "size": 0,
    "aggs": {
        "length_range": {
            "range": {
                "field": "length",
                "ranges" : [
                    {"key": "short", "to": 1000},
                    {"key": "medium", "from": 1000, "to": 3000},
                    {"key": "long", "from": 3000}
                ]
            }
        }
    }
}

结果:

{
    "aggregations": {
        "length_range": {
            "buckets": [
                {
                    "key": "short",
                    "to": 1000.0,
                    "doc_count": 1313
                },
                {
                    "key": "medium",
                    "from": 1000.0,
                    "to": 3000.0,
                    "doc_count": 481
                },
                {
                    "key": "long",
                    "from": 3000.0,
                    "doc_count": 845
                }
            ]
        }
    }
}

桶聚合+指标查询

桶聚合一般不单独使用,都是配合指标聚合一起使用,对数据分组之后肯定要统计桶内数据。

内置排序参数:

  • _count - 按文档数排序。对 terms, histogram, date_histogram 有效
  • _term - 按词项的字符串值的字母顺序排序。只在 terms 内使用
  • _key - 按每个桶的键值数值排序, 仅对 histogram 和 date_histogram 有效

或者可以按指标排序,order 的 key 是 聚合指标的自定义名字。

terms分组+sum总和倒序

先 terms 按 title.keyword 分桶,计算每个桶内的 length 字段 sum 总和,结果按 sum 倒序

{
  "size": 0,
  "aggs": {
    "group_by_title": {   // 按 title 分桶
      "terms": {
        "field": "title.keyword",
        "size": 3,
        "order": {
          "sum_length": "desc"   // 按嵌套聚合查询 sum_length 倒序排序
        }
      },
      "aggs": {  // 嵌套聚合查询,桶内按 length 求和
        "sum_length": {
          "sum": {
            "field": "length"
          }
        }
      }
    }
  }
}
{
    "aggregations": {
        "group_by_title": {
            "doc_count_error_upper_bound": -1,
            "sum_other_doc_count": 1125,
            "buckets": [
                {
                    "key": "算法1",
                    "doc_count": 3,
                    "sum_length": {
                        "value": 10073.0
                    }
                },
                {
                    "key": "数据1",
                    "doc_count": 3,
                    "sum_length": {
                        "value": 9331.0
                    }
                },
                {
                    "key": "算力1",
                    "doc_count": 2,
                    "sum_length": {
                        "value": 6665.0
                    }
                }
            ]
        }
    }
}

SQL查询

POST _sql?format=txt
{
  "query": "SELECT title, count(*) FROM my_index group by title order by count(*) desc limit 10"
}

sql转dsl

POST /_sql/translate
{
  "query": "sql"
}

https://segmentfault.com/a/1190000038394618


Elasticsearch SQL 客户端

SQL CLI https://www.elastic.co/guide/en/elasticsearch/reference/7.17/sql-cli.html

/usr/share/elasticsearch/bin 目录中有个jar包 elasticsearch-sql-cli-7.6.1.jar 可直接 java -jar 执行,连接指定的 Elasticsearch,连接后执行 sql,例如:

java -jar elasticsearch-sql-cli-7.6.1.jar http://10.153.106.19:8200
sql> select title, count(*) from my_index group by title order by count(*) desc limit 10;
     title     |   count(*)    
---------------+------------
title1         |7              
title2         |6              

搜索你的数据

Elasticsearch Guide [7.17] » Search your data https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-your-data.html


高亮

Elastic Docs ›Elasticsearch Guide [8.6] ›Search your data > Highlightingedit https://www.elastic.co/guide/en/elasticsearch/reference/8.6/highlighting.html

fields 指定高亮字段 pre_tags, post_tags 包围高亮的 HTML 标签,默认 pre_tags 是 <em>,默认 post_tags 是 </em> fragment_size 指定单个高亮片段(fragment)的长度

例1,指定高亮字段 body

GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "fields" : {
      "body" : {}
    }
  }
}

例2,指定高亮字段 body 和 前后标签

GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "pre_tags" : ["<tag1>"],
    "post_tags" : ["</tag1>"],
    "fields" : {
      "body" : {}
    }
  }
}

近实时/准实时(near-realtime)搜索

Elasticsearch Guide [7.17] » Search your data » Near real-time search https://www.elastic.co/guide/en/elasticsearch/reference/current/near-real-time.html

elastic 底层采用的是 lucene 这个库来实现倒排索引的功能,在 lucene 的概念里每一条记录称为 document(文档),lucene 使用 segment(分段) 来存储数据。Segment 是最小的数据存储单元。其中包含了文档中的词汇字典、词汇字典的倒排索引以及 Document 的字段数据。因此 Segment 直接提供了搜索功能。但是 Segment 能提供搜索的前提是数据必须被提交(Lucene commit),即文档经过一系列的处理之后生成倒排索引等一系列数据。

由于 Lucene commit 非常耗时,es 并不会每收到一条数据就提交一次,而是维护一个 Lucene 内部的 in-memory buffer(内存缓存区),新增一条记录时,es 只是将数据写入内存缓存区,每隔一段时间(默认为1秒),Elasticsearch 会执行一次 refresh 操作:lucene中所有的缓存数据都被写入到一个新的Segment,清空缓存数据。此时数据就可以被搜索。当然,每次执行refresh操作都会生成一个新的Segment文件,这样一来Segment文件有大有小,相当碎片化。Elasticsearch内部会开启一个线程将小的Segment合并(Merge)成大的Segment,减少碎片化,降低文件打开数,提升IO性能。

新增一条记录时,es 会把数据写到 translog 和 in-memory buffer(内存缓存区) 中,数据写入内存缓冲区后并不能立即被搜索到,需要等 refresh 操作(默认1秒一次)执行后刷入 segment 才能被搜索到,所以 es 搜索是 near-realtime 近实时 的,而不是实时的。


ES 查询优化

和美大家说 | Elasticsearch之相关性调整实践 https://mp.weixin.qq.com/s/kcCfP8zyGyKiOQdCOvzs_Q


文本分析

POST /_analyze 分析文本

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Analyze API https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-analyze.html

对一段文本进行分词

GET /_analyze
POST /_analyze
GET /<index>/_analyze
POST /<index>/_analyze

参数 text 要分析的文本。必填。 field 有 field 时会使用 field 的默认分析器。 analyzer 分析器,可以是内置分析器,也可以是自定义分析器。如果不设置,使用 field 的分析器。如果不指定 field,使用 index 的默认分析器。如果也不指定 index,或者 index 没有默认分析器,使用 standard 分析器。 tokenizer 分词器。将 text 转换为 token 的分词器。

不指定额外分词器时,es 使用一个名为 standard 的默认分析器,对于中文,standard 分词器只是将汉字拆分成一个个的汉字。

例1,测试 ik_smart 分析器:

GET /_analyze
{
    "text": "中国的",
    "analyzer": "ik_smart"
}

返回分词结果

{
  "tokens": [
    {
      "token": "中国",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "的",
      "start_offset": 2,
      "end_offset": 3,
      "type": "CN_CHAR",
      "position": 1
    }
  ]
}

例2,测试 分词器 和 词过滤器

POST _analyze
{
  "tokenizer": "standard",
  "filter":  [ "lowercase", "asciifolding" ],
  "text":      "Is this déja vu?"
}

apdplat.word 中文分词器

Java分布式中文分词组件 - word分词 https://github.com/ysc/word

@Test
public void testSeg() {
    String text = "Sitemap无需机械式包含所有链接,而应根据SEO目标、页面重要性和技术限制动态调整。";
    List<Word> words = WordSegmenter.seg(text);
    words.forEach(System.out::println);
}
// 结果
// Sitemap 无需 机械式 包含 链接 应根据 SEO 目标 页面 重要性 动态 调整

ik_max_word/ik_smart 最佳实践

ik_max_word 和 ik_smart

  • ik_max_word: 会将文本做最细粒度的拆分,会尽可能多地切分词语,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query;
  • ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”,适合 Phrase 查询。

两种分词器使用的最佳实践是:索引时用ik_max_word,在搜索时用ik_smart

  • 索引时,使用细粒度分词 ik_max_word 尽可能拆解更多潜在关键词。
  • 搜索时,使用粗粒度分词 ik_smart 提高搜索精准度。

例如,创建索引时指定 content 字段的 索引分析器 analyzer 和 搜索分析器 search_analyzer

PUT /my_index
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "ik_max_word",  // 索引时分词
        "search_analyzer": "ik_smart"  // 搜索时分词
      }
    }
  }
}

例如,ik_max_word 和 ik_smart 分词效果对比

GET /_analyze
{
    "analyzer": "ik_smart",
  "text" : "怪兽小当家,我是masikkk"
}

分词结果

{
    "tokens": [
        {
            "token": "怪兽",
            "start_offset": 0,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "小",
            "start_offset": 2,
            "end_offset": 3,
            "type": "CN_CHAR",
            "position": 1
        },
        {
            "token": "当家",
            "start_offset": 3,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 2
        },
        {
            "token": "我",
            "start_offset": 6,
            "end_offset": 7,
            "type": "CN_CHAR",
            "position": 3
        },
        {
            "token": "是",
            "start_offset": 7,
            "end_offset": 8,
            "type": "CN_CHAR",
            "position": 4
        },
        {
            "token": "masikkk",
            "start_offset": 8,
            "end_offset": 15,
            "type": "ENGLISH",
            "position": 5
        }
    ]
}

里面有 ik 分词演示 SpringBoot集成Elasticsearch7.4 实战(一) https://www.jianshu.com/p/1fbfde2aefa5


Elasticsearch 内置分析器

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-analyzers.html

Elasticsearch 内置分析器

  • Standard Analyzer 这是默认的analyzer。使用 standard tokenizer 将文本分成单词,然后使用 lowercase token filter将单词转换为小写。
  • Simple Analyzer 使用非单词字符将文本分成单词,然后使用 lowercase token filter将单词转换为小写。
  • Whitespace Analyzer 使用空白字符将文本分成单词。不执行小写转换或其他标记过滤。
  • Stop Analyzersimple analyzer 类似,但会删除停用词(如“and”、“the”等)。停用词列表是内置的,但也可以自定义。
  • Keyword Analyzer 将整个字段值作为单个单词输出。不进行分词、小写转换或其他标记过滤。
  • Pattern Analyzer 使用正则表达式来匹配并生成标记。默认情况下,它使用\W+(非单词字符)来分割文本,但你可以指定自己的模式。
  • Language Analyzers Elasticsearch 提供了许多针对特定语言的analyzer,如englishfrenchgerman等。这些analyzer通常包括针对该语言的特定分词和标记过滤规则,如词干提取(stemming)和词形还原(lemmatization)。
  • Fingerprint Analyzer 特殊用途的分析器,用于计算文本的指纹,用于重复检测。

要在 Elasticsearch 中使用这些analyzer,你可以在索引映射(index mapping)中指定它们。例如,在创建索引时,设置字段的 analyzer 参数,指定分析器。


创建自定义分析器

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-custom-analyzer.html

一个分析器 analyzer 包括:

  • 0个或多个字符过滤器(character filters)
  • 一个分词器(tokenizer)
  • 0个或多个词过滤器(token filters)

参数: type 对于自定义分析器,类型为 custom tokenizer (必须)分词器,可使用内置分词器,或三方插件分词器(例如ik) char_filter (可选)字符过滤器数组,内置字符过滤器或自定义字符过滤器 filter (可选)词过滤器数组,内置token过滤器或自定义token过滤器


基于ik分词器+同义词+停用词创建自定义分析器

1、关闭索引 2、创建自定义分析器 3、打开索引

如果 ik 的词库发生改变,实测基于 ik 自定义的分析器必须关闭索引、设置分析器、打开索引后,新加的词才能生效

POST /my_index/_close

PUT /my_index/_settings
{
  "analysis": {
    "filter": {
      "my_synonyms": {
        "type": "synonym_graph",
        "synonyms": [
          "项目,案例"
        ]
      },
      "my_stop": {
        "type": "stop",
        "ignore_case": true,
        "stopwords": ["的", "一", "不", "在", "人", "有", "是", "为", "以", "于", "上", "他", "而", "后", "之", "来", "及", "了", "因", "下", "可", "到", "由", "这", "与", "也", "此", "但", "并", "个", "其", "已", "无", "小", "我", "们", "起", "最", "再", "今", "去", "好", "只", "又", "或", "很", "亦", "某", "把", "那", "你", "乃", "它", "吧", "被", "比", "别", "趁", "当", "从", "到", "得", "打", "凡", "儿", "尔", "该", "各", "给", "跟", "和", "何", "还", "即", "几", "既", "看", "据", "距", "靠", "啦", "了", "另", "么", "每", "们", "嘛", "拿", "哪", "那", "您", "凭", "且", "却", "让", "仍", "啥", "如", "若", "使", "谁", "虽", "随", "同", "所", "她", "哇", "嗡", "往", "哪", "些", "向", "沿", "哟", "用", "于", "咱", "则", "怎", "曾", "至", "致", "着", "诸", "自"]
      }
    },
    "analyzer": {
      "ik_max_custom": {
        "type": "custom",
        "tokenizer": "ik_max_word",
        "filter": [
          "my_synonyms", "my_stop"
        ]
      },
      "ik_smart_custom": {
        "type": "custom",
        "tokenizer": "ik_smart",
        "filter": [
          "my_synonyms", "my_stop"
        ]
      }
    }
  }
}

POST /my_index/_open

Character Filter 字符过滤器

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-charfilters.html

Elasticsearch 中的 Character Filters(字符过滤器)是用于在将字符流传递给分词器(Tokenizer)之前对其进行预处理的工具。这些过滤器可以接收原始文本作为字符流,并通过添加、删除或更改字符来转换流。

内置字符过滤器类型:

  • html_strip HTML Strip Character Filter,去除 html 中的标签,或者将 html 中的标签转换为对应的 html 编码,例如将 <b> 转换为其对应的编码 &amp;
  • mapping 字符映射,使用指定的替换字符替换文本中任何出现的指定字符串。例如,将所有出现的“foo”替换为“bar”
  • pattern_replace 使用正则表达式匹配文本中的字符,并将其替换为指定的内容。

例如,定义一个 mapping 字符映射过滤器

PUT /my-index-000001
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "standard",
          "char_filter": [
            "my_mappings_char_filter"
          ]
        }
      },
      "char_filter": {
        "my_mappings_char_filter": {
          "type": "mapping",
          "mappings": [
            ":) => _happy_",
            ":( => _sad_"
          ]
        }
      }
    }
  }
}

Tokenizer 分词器

Elasticsearch Guide [7.17] ›Text analysis ›Tokenizer reference https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-tokenizers.html


Token Filter 词过滤器

Elasticsearch Guide [7.17] ›Text analysis ›Token filter reference https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-tokenfilters.html

词过滤器(Token Filter) 对 分词器(tokenizer) 产生的 token流 进行修改(例如变小写 lowercasing)、删除(例如删除停用词 stopwords)、添加(例如同义词 synonyms)。 Elasticsearch 内置了多个词过滤器。


stop 停用词过滤器

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-stop-tokenfilter.html

停用词也叫停止词,是指文本在被分词之后的词语中包含的没有搜索意义的词,例如 ”的地得“。

停用词过滤器 从分词器产生的 token流 中删除停用词,不指定时,默认会删除下面这些英文停用词:

a, an, and, are, as, at, be, but, by, for, if, in, into, is, it, no, not, of, on, or, such, that, the, their, then, there, these, they, this, to, was, will, with

中文停用词 https://www.ranks.nl/stopwords/chinese-stopwords

参数 stopwords 停用词数组 ignore_case 默认false,是否忽略大小写


synonym 同义词过滤器

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-synonym-tokenfilter.html


synonym_graph 多同义词过滤器

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-synonym-graph-tokenfilter.html

synonym_graph 可处理多单词同义词,且仅可在搜索分析器中使用。

创建 synonym_graph 过滤器,指定同义词配置文件 analysis/synonym.txt,然后创建自定义分词器 search_synonyms 指定使用 graph_synonyms 同义词过滤器:

PUT /test_index
{
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "search_synonyms": {
            "tokenizer": "whitespace",
            "filter": [ "graph_synonyms" ]
          }
        },
        "filter": {
          "graph_synonyms": {
            "type": "synonym_graph",
            "synonyms_path": "analysis/synonym.txt"
          }
        }
      }
    }
  }
}

analysis/synonym.txt 是相对于 config 目录的文件。

参数: expand 是否进行同义词扩展,默认 true lenient 默认false,设为true时可忽略同义词配置文件中的错误

也可以不使用配置文件,直接在请求参数中配置同义词

PUT /test_index
{
  "settings": {
    "index": {
      "analysis": {
        "filter": {
          "synonym": {
            "type": "synonym_graph",
            "synonyms": [
              "lol, laughing out loud",
              "universe, cosmos"
            ]
          }
        }
      }
    }
  }
}
Elasticsearch 同义词配置格式

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-synonym-graph-tokenfilter.html#_solr_synonyms_2

# => 左边的词替换为所有右边的词
i-pod, i pod => ipod
sea biscuit, sea biscit => seabiscuit

# 逗号分割同义词,expand=true 时逗号分割的词可任意替换
ipod, i-pod, i pod
foozball , foosball
universe , cosmos
lol, laughing out loud

# expand=true 时,等同于 ipod, i-pod, i pod
ipod, i-pod, i pod => ipod, i-pod, i pod

# expand=false 时,等同于 ipod, i-pod, i pod
ipod, i-pod, i pod => ipod

# 相同的左侧 key 映射会自动合并
foo => foo bar
foo => baz
# 等同于:
foo => foo bar, baz

synonym 同义词

一样,却又不同:借助同义词让 Elasticsearch 更加强大 https://www.elastic.co/cn/blog/boosting-the-power-of-elasticsearch-with-synonyms

Elasticsearch:使用同义词 synonyms 来提高搜索效率 https://blog.csdn.net/UbuntuTouch/article/details/108003222

索引时同义词 VS 搜索时同义词

可在索引时使用同义词,也可在搜索时使用。

1、在索引时应用同义词 索引文档时进行一次性同义词替换或扩展,结果将一直保存在搜索索引中。 优势是性能好,因为在索引文档时已经做了同义词扩展,无需再在每次查询时完成一遍扩展过程。 索引时使用同义词有几个劣势:

  • 由于必须对所有同义词进行索引,所以索引规模会变大。
  • 搜索得分(依赖于字词统计数据)可能会受影响,因为同义词也会计算在内,所以不常见单词的统计数据会存在偏差。
  • 除非进行重新索引,否则无法针对既有文档更改同义词规则。

后2点劣势较严重,不建议使用。

2、搜索时使用同义词 劣势是每次查询时都必须执行同义词扩展操作 优势:

  • 索引规模不受影响。
  • 语料库中的字词统计数据保持不变。
  • 如需变更同义词规则,无需对文档进行重新索引。

此外,搜索时扩展同义词还能够允许使用更加复杂的 synonym_graph 词过滤器。synonym_graph 可处理多单词同义词,且仅可在搜索分析器中使用。

创建/更新同义词必须关闭索引

尽管更改同义词规则不需要对文档进行重新索引,但是如要更改的话,必须暂时关闭再重新打开索引。 因为分析器在下列时候才会创建实例:创建索引时,重启节点时,以及重新打开已关闭的索引时。


Token Graph 词图

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/token-graphs.html

分词器将文本转换为 token 流,同时也会记录每个 token 的位置。 position 0, 1, 2 作为顶点,token 作为边(弧),可以创建一个 DAG 图。 例如 quick brown fox: 1 -quick-> 1 -brown-> 2 -fox-> 3