Appearance
ES中的查询
本文介绍ES中的查询相关知识。
1. 查询概述
在ES中,查询语言是通过JSON定义的,称为Query DSL(Domain Specific Language)。Query DSL是ES最初,也是功能最强大的查询语言,可以用于查询、过滤、聚合数据等。
_search API用于执行查询操作,接受Query DSL作为请求体,用于定义查询逻辑。
1.1 _search
_search API 用于定义执行查询操作,语法如下:
txt
GET /{index}/_search
{
// request body
}- {index}:定义要查询的索引名称;
- request body:用于定义查询体,包括Query DSL 定义的查询逻辑、分页、排序、聚合等等;
- query:容纳Query DSL定义的查询逻辑;
- size和from:用于定义分页;
- sort:用于定义排序规则;
- aggregations:用于定义聚合;
具体的用法在下面例子中演示。
1.2 叶子查询与复合查询
在ES中,查询分为两种:
Leaf Query(叶子查询):叶子查询是在某个字段中寻找特定值,例如
match、term或range查询,叶子查询可以单独使用;Compound Query(复合查询) :复合查询包含其他的叶子查询或复合查询,用于组合多个查询条件,例如
bool、dis_max查询;
1.3 查询上下文
在之前的倒排索引介绍中,如果进行全文匹配,那么文档和查询条件会有匹配分数,称为相关性得分。
默认情绪下,ES会将查询结果文档按照相关性得分进行排序。相关性得分是一个正浮点数,分数越高,表示该文档与查询条件越匹配。每种查询类型计算相关性得分的方式不一样,并且是否计算相关性得分也取决于查询上下文。
查询上下文分为两种:Query Context和Filter Context。
Query Context(查询上下文):在查询上下文中,可以回答某文档与查询条件的匹配度是多少这个问题,也就是说,在查询上下文中,会计算查询条件与文档的相关性得分。
当查询条件在
_searchAPI 的query参数中,就说明查询条件在查询上下文中了。Filter Context(过滤上下文):过滤回答这个文档是否匹配查询条件,答案为布尔值:是或否。**换句话说,过滤不计算文档与查询条件的相关性得分。**过滤有以下优点:
- 逻辑简单:过滤仅仅决定一个文档是否匹配查询条件,不计算相关性;
- 性能好与资源消耗少:由于不计算相关性得分,所以性能好,并且使用更少的CPU;
- 缓存:ES能缓存最近使用的过滤条件和结果,加速之后的相同过滤;
- 查询组合:可以通过将过滤器与评分查询结合来有效地细化结果集;
过滤适合用在结构化的数据类型上,例如数字类型、日期时间类型、布尔值、
keyword类型、空间数据类型。常见过滤类型包括:- 时间范围过滤:例如,日期类型字段
birth是否在2000到2001之间; - 特定值匹配过滤:例如,
keyword类型字段status值是否等于published;
当查询条件出现在以下位置时,说明这些查询条件就处在过滤上下文中:
- 出现在
bool查询中的filter和must_not参数中; - 出现在
constant_score查询中的filter参数中; - 出现在
filter聚合条件中;
下面的例子演示查询上下文和过滤上下文(可暂时跳过):
txt
GET /xxx/_search
{
"query": { // 1
"bool": { // 2
"must": [ // 3
{ "match": { "title": "Search" }},
{ "match": { "content": "Elasticsearch" }}
],
"filter": [ // 4
{ "term": { "status": "published" }},
{ "range": { "publish_date": { "gte": "2015-01-01" }}}
]
}
}
}- 位置1的
query表示在其中的查询处于查询上下文中; - 位置2的
bool表示是一个复合查询; - 位置3的
must表示必须匹配的条件,其中的两个match查询处于查询上下文中,所以会计算文档与这两个查询条件的相关性得分; - 位置4的
filter表示在其中的查询处于过滤上下文中,所以其中的term和range查询不会计算相关性得分;
关于查询上下文和过滤上下文的执行顺序?对于某个查询而言,如果其中涉及了查询上下文和过滤上下文,那么ES会按照以下的顺序执行:
- 应用过滤上下文中的查询条件,缩小候选结果集,并且ES会对过滤器及结果进行缓存,提高后续查询的效率;
- 应用查询上下文中的查询条件,计算相关性得分;
- 按照相关性得分排序,返回最匹配的文档;
TIP
在查询上下文中使用会影响匹配文档得分的查询条件,而在过滤上下文中使用其他查询条件。
1.4 数据准备
在进行实际的查询操作前,需要准备测试数据,好在ES准备了一些示例数据集。在Kibana界面,找到Integrations界面,搜索Sample Data:

在示例数据界面,有三个测试数据集,我们选择安装全部测试数据集:

2. 基础叶子查询
2.1 等值匹配
在ES中,使用term查询来进行等值匹配,基本语法如下:
json
"term":{
"<field>":{
"value": "xxx",
"boost": 1.0,
"case_insensitive": true
}
}<field>:必需,用于指定要匹配的字段名;value:必需,字符串类型,用于指定要匹配的值;boost:可选,浮点数类型,用于改变相关性得分,默认值为1.0。改变方式就是将原始的文档相关性得分与boost的值相乘;case_insensitive:可选,布尔值类型,用于控制是否忽略大小写,默认值为false;
注意,value的类型是字符串类型,并不是说term的等值匹配只能匹配字符串,可以匹配数字类型、布尔类型、日期时间类型、keyword类型。
CAUTION
term查询不能用于匹配text类型的字段。
下面的例子演示了使用term查询kibana_sample_data_logs中的文档,查询条件为clientip的值等于223.87.60.27,并且限制查询返回一个文档:
txt
GET /kibana_sample_data_logs/_search
{
"query": {
"term": {
"clientip": {
"value": "223.87.60.27",
"boost": 1
}
}
},
"from": 0,
"size": 1
}结果如下:
Details
json
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 17,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "kibana_sample_data_logs",
"_type" : "_doc",
"_id" : "gxWArpkB7vy-AHMxLXTF",
"_score" : 1.0,
"_source" : {
"agent" : "Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1",
"bytes" : 6219,
"clientip" : "223.87.60.27",
"extension" : "deb",
"geo" : {
"srcdest" : "US:US",
"src" : "US",
"dest" : "US",
"coordinates" : {
"lat" : 39.41042861,
"lon" : -88.8454325
}
},
"host" : "artifacts.elastic.co",
"index" : "kibana_sample_data_logs",
"ip" : "223.87.60.27",
"machine" : {
"ram" : 8589934592,
"os" : "win 8"
},
"memory" : null,
"message" : "223.87.60.27 - - [2018-07-22T00:39:02.912Z] \"GET /elasticsearch/elasticsearch-6.3.2.deb_1 HTTP/1.1\" 200 6219 \"-\" \"Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1\"",
"phpmemory" : null,
"referer" : "http://twitter.com/success/wendy-lawrence",
"request" : "/elasticsearch/elasticsearch-6.3.2.deb",
"response" : 200,
"tags" : [
"success",
"info"
],
"timestamp" : "2025-09-21T00:39:02.912Z",
"url" : "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.deb_1",
"utc_time" : "2025-09-21T00:39:02.912Z",
"event" : {
"dataset" : "sample_web_logs"
}
}
}
]
}
}TIP
解读查询结果
目前只需要关注查询结果中的hits内容,其中又包括三个字段:
total:提供了关于匹配文档总数的信息。
value:查询实际匹配到的文档总数量;
relation:字段表示
value的准确性,取值为eq或gte。当值为
eq时,表示匹配查询条件的文档数就是value字段值。在默认情况下,当一个查询的匹配文档数量非常大(例如超过 10000 个)时,ES 会停止精确计数,以节省时间和系统资源。此时,
relation的值为gte,表示至少有 10000 个文档匹配,此时value的值为10000。
max_score:表示文档与查询最大的相关性得分;
hits:是一个数组,每个元素表示匹配的文档,其中包括字段:
- _index:索引名称;
- _id:文档ID,由ES自动生成或在创建文档时由用户赋予;
- _score:文档相关性得分;
- _source:原始文档;
2.2 IN查询
在ES中,IN查询使用terms,即判断某个文档字段的值是否包含特定的值,语法如下:
json
"terms":{
"<field>": ["v1", "v2"],
"boost": 1.0
}<field>:必需,文档字段名称;["v1", "v2"]:必需,需要匹配的值;boost:可选,用于改变相关性得分;
例如,在kibana_sample_data_logs索引中,geo是object类型,src是其中一个字段,并且只有一个类型为keyword的值,使用terms查询geo.src的值为US或UK的文档:
txt
GET /kibana_sample_data_logs/_search
{
"query": {
"terms": {
"geo.src": [
"US",
"UK"
]
}
}
}再例如,tags在文档中是数组类型,下面的terms查询表示,只要文档中的值(数组)包含任意查询数组["success", "info"]的值,那么就表示该文档匹配查询:
txt
GET /kibana_sample_data_logs/_search
{
"query": {
"terms": {
"tags": [
"success",
"info"
]
}
}
}terms查询与term查询相同,区别在于可以搜索多个值。只要文档包含至少一个查询的术语就会匹配。如果要查找包含多个匹配术语的文档,可以使用terms_set查询。
terms_set 查询是 Elasticsearch (ES) 中一种特殊的查询类型,它用于实现 集合匹配 的逻辑。它的核心目的是判断一个文档的特定字段中包含的值,是否达到或超过了用户在查询中设置的最小匹配数量。简单来说,它不是问“文档有没有这些值?”,而是问“文档有没有足够多的这些值?”
terms_set基础语法如下:
txt
"terms_set": {
"<field>": {
"terms": [ "v1", "v2", "v3" ],
"minimum_should_match": 2,
"minimum_should_match_field": "required_matches"
}
}<field>:必需,用于指定需要匹配的字段名称;terms:必需,值列表,包含所有可能匹配项的列表;minimum_should_match:可选,最小匹配数;minimum_should_match_field:可选,最小匹配字段,文档中一个数字字段的名称。这个字段存储了该文档必须匹配的最小项数;
2.3 范围查询
在ES中,范围查询使用range查询,语法如下:
txt
"range": {
"<field>": {
"gte": 10,
"gt": 10,
"lte": 20,
"lt": 20,
"format": "yyyy-MM-dd HH:mm:ss",
"time_zone": "-08:00"
}
}<field>:要查询的字段名称;gte:字段值需要大于等于的值;gt:字段值需要大于的值;lte:字段值需要小于等于的值;lt:字段值需要小于的值;format:用于转换在查询条件中的日期时间格式,方便ES转换为时间戳进行比较;默认使用Mapping中字段<field>定义的格式;time_zone:用于定义时区,取值为ISO 8601 UTC定义的偏移值,例如-08:00或时区ID,例如Asia/Shanghai时区ID列表:https://docs.oracle.com/cd/E72987_01/wcs/tag-ref/MISC/TimeZones.html
例如,查询bytes的值在
txt
GET /kibana_sample_data_logs/_search
{
"query": {
"range": {
"bytes": {
"gt": 1000,
"lte": 2000
}
}
}
}再例如,查询时间范围:
txt
GET /kibana_sample_data_logs/_search
{
"query": {
"range": {
"timestamp": {
"gt": "2025-10-01 00:00:00",
"lte": "now",
"format": "yyyy-MM-dd HH:mm:ss",
"time_zone": "Asia/Shanghai"
}
}
}
}gt:匹配timestamp晚于 2025 年 10 月 1 日 00:00:00 的文档。lte:匹配timestamp早于或等于 当前执行搜索的时刻的文档。关于日期常量及日期计算:https://www.elastic.co/docs/reference/elasticsearch/rest-apis/common-options#date-math
format:指定了用于解析"gt"值("2025-10-01 00:00:00")的日期时间字符串格式。time_zone:它告诉 Elasticsearch,在执行范围比较之前,先将gt指定的日期时间(即2025-10-01 00:00:00)视为 "Asia/Shanghai" (上海/北京时间,即东八区) 的时间。
这个查询的完整逻辑是:
- 将输入的日期字符串
2025-10-01 00:00:00解释为 东八区 (Asia/Shanghai) 的时间。 - 将这个东八区的时间转换成 Elasticsearch 内部使用的 UTC 时间(协调世界时)。
- 计算:东八区比 UTC 快 8 小时,所以
2025-10-01 00:00:00 +08:00等于2025-09-30 16:00:00Z(UTC)。
- 计算:东八区比 UTC 快 8 小时,所以
- 搜索所有
timestamp字段大于这个 UTC 转换时间点,并且小于等于搜索执行当前时刻的文档。
简而言之,它搜索的是从 2025年10月1日零点(北京时间) 开始,直到现在为止的所有日志数据。
TIP
注意,在查询时指定的时区需要与存储的时区一致,否则可能出现不准确的问题。
2.4 是否存在查询
在ES中,判断某个字段是否存在,可以使用exists查询,语法如下:
json
"exists": {
"field": "xx"
}field:值为必须存在的字段名称;
在以下情况下,字段视为不存在:
- 字段值为
null或[]; - 字段属性设置如下:
"index" : false并且"doc_values" : false,这样表示字段值不被索引,ES无法搜索字段值,所以认为字段不存在; - 如果字段值长度超过了
ignore_above设置的长度,那么该字段值不会被索引,因此搜索不到被认为不存在; - 如果字段格式格式错误,并且设置了
ignore_malformed,那么也会被认为不存在;
在以下情况下,字段视为存在:
- 字段值为空字符串,例如
""或"-"; - 数组包括
null值和其他值,例如[null, "a"]; - 在字段mapping中定义了
null_value属性;
例如:
txt
GET /kibana_sample_data_logs/_search
{
"query": {
"exists": {
"field": "memory"
}
}
}2.5 模糊匹配
在ES中,模糊匹配有以下类型:
- Fuzzy:基于编辑距离的模糊匹配;
- Prefix:前缀匹配;
- Regexp:正则表达式匹配;
- Wildcard:通配符匹配;
2.5.1 Fuzzy
ES 的 Fuzzy匹配是基于 编辑距离(Edit Distance) 理论实现的,最常用的是 莱文斯坦距离(Levenshtein Distance)。
编辑距离衡量的是将一个字符串(搜索词)转换成另一个字符串(文档字段中的值)所需的最少单字符编辑操作次数。这些操作通常包括:
- 替换 (Substitution):将一个字符换成另一个。
- 插入 (Insertion):在某处增加一个字符。
- 删除 (Deletion):删除一个字符。
- 交换位置(transposition):交换相邻两个字符,例如 cat -> cta。
例如,将 "apple" 转换为 "aple" 需要删除一个 'p',编辑距离为 1。
Fuzzy匹配的语法如下:
txt
"fuzzy": {
"<field>": {
"value": "vvv",
"fuzziness": "AUTO",
"max_expansions": 50,
"prefix_length": 0,
"transpositions": true
}
}<field>:必需,需要模糊匹配的字段名称;value:必需,用户输入的、希望进行模糊匹配的原始字符串;fuzziness:核心参数。 定义允许的最大编辑距离(插入、删除、替换、交换操作的次数)。它可以是0、1、2,或者默认的AUTO(ES 会根据搜索词长度自动设置 1 或 2)。如果
fuzziness的值为0,表示精确匹配。max_expansions:ES 在词项字典中搜索符合模糊条件的变体词时,允许查找和检查的最大词项数量。默认值为50。假设现在需要进行模糊匹配的查询词是
apple,并且允许的最大编辑距离为2,那么在第一个字符处,仅仅进行英文字母的替换,那么就有25个匹配的模糊词,在第二个位置再进行替换,就有25*25个模糊匹配词,依次类推,可以认为匹配的模糊词数量非常巨大。所以,为了查询效率考虑,避免过度模糊匹配导致查询耗时过长,如果符合条件的变体数量达到了max_expansions设定的值,那么ES就会停止查找变体值。prefix_length:指定搜索词开头的多少个字符必须是精确匹配的,不计入编辑距离。默认值为0,意味着搜索词的所有字符都可以参与模糊匹配。例如,搜索词是 "ki",如果设置为
prefix_length: 1,则第一个字符 'k' 必须精确匹配,模糊匹配只应用于剩下的 'i',例如'kk'。设置为0则允许 'k' 也被模糊处理,例如,匹配到 "ji" 或 "ko"。transpositions:定义是否将相邻字符的转置(例如将 "ta" 变成 "at")计为一次编辑操作(距离 1)。默认值为true,如果设置为false,转置会被视为两次操作(一次删除,一次插入)。
2.5.2 Prefix
Prefix查询用于查询某字段值以指定前缀开头的文档。格式如下:
txt
"prefix": {
"<field>": {
"value": "xx"
}
}<field>:用于指定字段名称;
也可以使用简写形式:
txt
"prefix" : {
"<field>" : "xx"
}通过在mapping中启用index_prefixes参数,可以加快前缀查询的速度。这样,Elasticsearch会根据配置设置在单独的字段中索引前缀,从而提高前缀查询的效率,但会增加索引的大小。
2.5.3 Regexp
Regexp 用于正则表达式匹配,语法如下:
txt
"regexp": {
"<field>": {
"value": "expression"
}
}<field>:用于指定字段名称;value:用于指定正则表达式,关于正则表达式语法,参考链接:https://www.elastic.co/docs/reference/query-languages/query-dsl/regexp-syntax
2.5.4 Wildcard
Wildcard 查询用于通配符匹配,主要有两个特殊符号:
*:匹配任意多个字符,可以匹配 0 个或多个字符;?:匹配单个任意字符,必须且只能匹配 1 个字符;
语法如下:
txt
"wildcard": {
"<field>": {
"value": "abc*"
}
}<field>:指定需要匹配的字段名称;value:指定匹配表达式;
3. 叶子查询之全文匹配
使用ES最大的原因就是做搜索,也就是全文匹配,ES提供了以下的全文匹配方式。
3.1 match
match 查询是最基本的全文匹配方式,语法如下(基础设置):
txt
"match": {
"<field>": {
"query": "text to be searched",
"analyzer": "ik_max_word",
"operator": "and"
}
}<field>:指定需要进行全文匹配的字段名称,一般该字段为text类型;query:指定用户输入的查询语句;analyzer:指定分词器,默认使用索引构建时该字段的分词器;operator:指定分词后的词元查询关系,可选值为or和and:or:默认值,假设query中的值为capital of Hungary,那么分词后为[capital、of、Hungary],查询时使用or关联,即capital OR of OR Hungary;and:即查询时使用and关联词元,即capital AND of AND Hungary;
match 查询也有简写形式:
txt
"match": {
"<field>": "text to be searched"
}例如:
txt
GET /kibana_sample_data_logs/_search
{
"query": {
"match": {
"agent": {
"query": "linux chrome windows",
"operator": "or"
}
}
}
}如果将operator改为and,会发现查询不到结果。
3.2 match phrase
match_phrase 查询是 Elasticsearch (ES) 中用于实现短语搜索 (Phrase Search) 的核心工具。
它与普通的 match 查询最大的区别在于:它不仅要求文档中包含所有搜索词,还要求这些词必须以完全相同的顺序、相邻地出现在文档中。简而言之,它查找的是精确的词组或句子。
语法如下:
txt
"match_phrase": {
"<field>": {
"query": "text to be searched",
"analyzer": "ik_smart",
"slop": 1
}
}<field>:指定需要查询的字段;query:查询词组或句子;analyzer:分词器,注意,虽然match_phrase看似不需要分词,实际上ES仍然会分词,只是匹配的时候保证了分词后的词元顺序保持不变;slop:容错性,即允许词项之间被其他词语隔开,或者顺序发生轻微的调整。默认值为0,表示精确匹配。slop:0:假设查询词为quick fox,那么表示精确匹配,只匹配"quick fox",不允许任何间隔或顺序变化;slop:1:允许一次间隔,假设查询词为quick fox,则可以匹配"quick brown fox";slop:2:允许两次间隔或一次转置(交换相邻单词的位置),假设查询词为quick fox,则可以匹配"quick brown beautiful fox"或fox quick;
注意,如果查询词包含多个单词,例如
text to searched,那么slop的值表示累加值,并不相邻两个单词之间都可以插入slop个间隔词。
例如:
txt
GET /kibana_sample_data_logs/_search
{
"query": {
"match_phrase": {
"agent": {
"query": "text to searched",
"slop": 2
}
}
}
}3.3 multi match
TIP
在了解multi match之前,可以先了解复合查询的内容。
multi match可以在多个字段上进行全文匹配,语法如下:
txt
"multi_match" : {
"query": "brown fox",
"fields": [ "subject", "message" ],
"type": "best_fields"
}query:查询字符串;fields:字段名称数组,表示要在哪些字段上应用模糊查询;type:查询类型,定义了multi_match如何执行查询,取值如下:best_fields:默认值,类似于dis_max查询,文档最终得分取决于得分最高的字段(如果设置了tie_breaker,则加上其他字段得分);most_fields:文档得分是各字段得分的总和;cross_fields:针对分词后的词元进行匹配;
还有其他取值,例如
phrase、phrase_prefix、bool_prefix,此处不过多介绍。
下面具体介绍不同type取值,ES是如何执行匹配的。
best_fields
假设有下面的查询:
txt
GET /xxx/_search
{
"query": {
"multi_match" : {
"query": "brown fox",
"type": "best_fields",
"fields": [ "subject", "message" ],
"tie_breaker": 0.3
}
}
}转换为dis_max查询,如下:
txt
GET /xxx/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "subject": "brown fox" }},
{ "match": { "message": "brown fox" }}
],
"tie_breaker": 0.3
}
}
}在multi_match中,仍然可以设置operator等match查询中的参数,例如:
txt
GET /xxx/_search
{
"query": {
"multi_match" : {
"query": "brown fox",
"type": "best_fields",
"fields": [ "subject", "message" ],
"tie_breaker": 0.3,
"operator": "and"
}
}
}转换为dis_max结果如下:
txt
GET /xxx/_search
{
"query": {
"dis_max": {
"queries": [
{
"match": {
"subject": {
"query": "brown fox",
"operator": "and"
}
}
},
{
"match": {
"message": {
"query": "brown fox",
"operator": "and"
}
}
}
],
"tie_breaker": 0.3
}
}
}most_fields
假设有如下查询:
txt
GET /_search
{
"query": {
"multi_match" : {
"query": "quick brown fox",
"type": "most_fields",
"fields": [ "title", "title.original", "title.shingles" ]
}
}
}ES执行逻辑如下:
txt
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "quick brown fox" }},
{ "match": { "title.original": "quick brown fox" }},
{ "match": { "title.shingles": "quick brown fox" }}
]
}
}
}同理,也可以接受match查询参数。
cross_fields
如果type值为cross_fields,那么查询是以分词后的词元为中心的。例如,现有如下查询:
txt
GET /_search
{
"query": {
"multi_match" : {
"query": "Will Smith",
"type": "cross_fields",
"fields": [ "first_name", "last_name" ]
}
}
}首先,ES会将查询条件分词,结果为"Will"和"Smith",然后,针对每个词元,判断每个字段是否包含该词元,
txt
((first_name contains Will) OR (last_name contains Will))
OR
((first_name contains Smith) OR (last_name contains Smith))cross_fields也可以接受operator参数,但是意义与best_fields不同,例如:
txt
GET /_search
{
"query": {
"multi_match" : {
"query": "Will Smith",
"type": "cross_fields",
"fields": [ "first_name", "last_name" ],
"operator": "and"
}
}
}执行逻辑为:
txt
((first_name contains Will) OR (last_name contains Will))
AND
((first_name contains Smith) OR (last_name contains Smith))关于cross_fields查询,可以参考combined_fields:
https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-combined-fields-query
3.4 query string
Query String 查询 是 Elasticsearch (ES) 中一种强大且灵活的搜索方式,它允许用户直接在查询字符串中输入类似于 Lucene 查询语法的语句。简而言之,它能像在 SQL 或编程语言中一样,使用 布尔逻辑 (AND,OR,NOT)、通配符 (∗,?)、范围 (>,<) 以及字段限定等复杂语法来构造搜索条件。
最基本的语法如下:
txt
"query_string": {
"query": "(status:active) OR (title:(quick OR brown))"
}在query中,是符合语法规范的查询条件。一些具体的语法规则和解释如下:
词元与操作符:使用
query_string查询时,查询字符串首先被分割为一系列词元与操作符,词元可以是单个词,例如quick,也可以是用双引号包围的词组,例如"quick brown",在词组中,每个单词的顺序不变。操作符在下面介绍。
字段名称:我们可以在查询字符串中直接指定字段名称,如
status;如果字段名称包含空格,需要使用转义符号,例如first\ name;字段名称也可以使用通配符,但需要结合转义符号使用,例如book.\*,表示book.name、book.content等以book.开头的字段;冒号
:的含义:在查询字符串中,:表示包含的意思,例如name:will,表示name包含will的意思,如果字段不是text类型,那么:就表示等于的意思;title:Elasticsearch:查找title字段包含 "Elasticsearch" 的文档;body:"quick brown fox":查找body字段包含精确短语 "quick brown fox" 的文档。
模糊搜索:在查询字符串中,也可以进行模糊匹配,主要有以下三种:
- 通配符:例如
name:Jo*,查找name以 "Jo" 开头的名称; - 模糊匹配:例如
name:smth~1,查找与 "smth" 编辑距离为 1 的词(如 "smith"); - 近似匹配:例如
name:"quick fox"~3,查找 "quick" 和 "fox" 之间最多间隔 3 个词的文档(即slop);
- 通配符:例如
范围查询:可以对数字或日期字段执行范围限定
- 大于和大于等于:例如,
num:>10或num:>=10; - 小于和小于等于:例如,
num:<10或num:<=10; - 闭区间范围查询:例如,
num:[1 TO 5],表示查询num在1-5之间的文档,包括1和5; - 开区间范围查询:例如,
date:{* TO 2012-01-01},表示查询date在2012-01-01之前的文档; *:在范围查询中,*表示无限;
- 大于和大于等于:例如,
布尔操作符:在查询字符串中,可以使用布尔操作符来连接多个查询条件,布尔操作符如下:
AND:逻辑与,例如
fruits:(apple AND orange),表示fruits必须同时包含apple和orange;OR:逻辑或,例如
fruits:(apple OR orange),表示fruits包含apple或orange即可;NOT或**
-**:逻辑非;例如
num:(NOT 10),表示num不等于10;再例如
fruits:(apple -orange),表示fruits必须包含 "apple",但不能包含 "orange";再例如,
"num:(21 -\\-20)",表示num必须包含21,但不能包含-20;+:表示必须,例如num:(+21 20),表示num必须包含21,包含20是可选的;
字段是否存在:可以使用
_exists_来查询某个字段是否存在的文档,例如_exists_:name表示name字段必需存在;空格的含义:在
query_string查询中,空格的含义取决于default_operator,默认值为OR;如下面的查询:
txt{ "query": { "query_string": { "query": "_exists_:age name:ww" } } }表示的含义是
age必须存在或name包含ww。我们可以设置
default_operator的值,改为AND:txt{ "query": { "query_string": { "query": "_exists_:age name:ww", "default_operator": "AND" } } }那么表示的含义是
age必须存在并且name包含ww。
4. 复合查询
复合查询是指组合多个叶子查询或其他复合查询的查询,也就是说包含多个查询条件的查询。
4.1 bool
bool 查询建立在Lucene中的BooleanQuery上,包含四种类型:
must:结果文档必须满足出现在must中的查询条件,并且must中的查询会影响相关性得分;should:如果结果文档满足出现在should中的查询条件,那么该文档的相关性得分会更高;must_not:结果文档不能满足出现在must_not中的查询条件,出现在must_not中的查询不会影响文档相关性得分;filter:结果文档必须满足出现在filter中的查询条件,但是filer中的查询不会影响相关性得分;
语法如下:
txt
"bool" : {
"must" : [{查询条件}],
"filter": [{查询条件1},{查询条件2}],
"must_not" : []
"should" : [],
"minimum_should_match" : 1,
"boost" : 1.0
}如果
must、filter、must_not、should中只包含一个查询条件,那么可以将查询条件数组改为对象,例如:txt"filter":{ "term":{ "name":{ "value": "zs" } } }
关于minimum_should_match参数的使用
minimum_should_match参数用于控制文档需要满足should中查询条件的最小个数。如果在bool查询中,只有should查询,没有must和filter查询,那么minimum_should_match的默认值为1,否则为0。
例如:
txt
GET /kibana_sample_data_logs/_search
{
"query": {
"bool": {
"should": {
"term": {
"clientip": "130.246.123.197"
}
},
"must": [
{
"match": {
"agent": "test"
}
}
]
}
}
}4.2 dis_max
Disjunction Max Query(简称为 dis_max 查询)是 Elasticsearch (ES) 中一种强大的多字段搜索工具,旨在解决这样一个问题:如何找到一个文档,使其在多个可能匹配的字段中,至少在一个字段上达到最好的相关性得分。
语法如下:
txt
"dis_max": {
"queries": [
{ 子查询 },
{ 子查询 }
],
"tie_breaker": 0.3
}queries:定义多个子查询;tie_breaker:平衡打破器,用于衡量除得分最高的字段之外的其他匹配字段对最终得分的贡献。默认值为0;
在dis_max查询中,最终文档的得分计算公式如下:
例如下面的例子:
txt
GET /products/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "quick fox" } }, // 查询 1: 标题
{ "match": { "tags": "quick fox" } }, // 查询 2: 标签
{ "match": { "description": "quick fox" } } // 查询 3: 描述
],
"tie_breaker": 0.3 // 允许其他匹配贡献 30% 的得分,打破平局
}
}
}在某个文档中,子查询的得分如下:
| 字段 | 匹配得分 |
|---|---|
| title | 0.8 |
| body | 0.4 |
| tags | 0.6 |
该文档最终的相关性得分计算如下:
如果使用dis_max,推荐将tie_breaker的值设置为0.1到0.7之间,以平衡最高得分和匹配广度。
4.3 constant score
constant_score查询包含一个filter过滤器,并且符合该过滤器的文档都有统一的相关性得分,由boost指定。语法如下:
txt
"constant_score": {
"filter": {
查询条件
},
"boost": 1.2
}4.4 function score
function_score可以用来改变相关性得分。其基本语法如下:
txt
"function_score": {
"query": { "match_all": {} },
"boost": 5,
"functions": [ ],
"score_mode": "max",
"max_boost": 42,
"boost_mode": "multiply",
"min_score": 42
}query:定义原始查询条件,注意,这里是JSON对象,如果有多个查询条件,应使用bool包装;boost:提升原始查询与文档的相关性得分,即将原始得分与boost的值相乘,再进行后续的计算;functions:定义一系列计算新得分的函数;score_mode:对于新计算出来的函数得分,由于有多个,如何组合这多个函数得分的策略;max_boost:最终的函数得分不能超过该值,如果超过,则使用该值,默认值为最大的单精度浮点数;boost_mode:对于最终的函数得分,与最初的相关性得分,这两个值如何组合形成最终的得分;min_score:对于最终的得分,应该大于该值,如果小于该值,那么该文档不会被返回;
对于function_score查询,整体流程可梳理如下:
- 首先应用原始查询条件,找到符合条件的文档,此时,该文档有一个初始相关性得分,称为
Initial Score,简称IS; - 然后,将相关性得分乘以
boost的值,得到Boosted Initial Score,简称BIS; - 然后,对每个文档应用多个函数,得到多个函数得分
Function Scores(FSs); - 按照
score_mode的策略,将多个函数得分组合(相加、相乘、最大值、最小值等),得到一个函数得分FS; - 将函数得分
FS与max_boost相比较,取较小值,得到最终函数得分Final Function Score(FFS),即FFS = MIN(FS, max_boost); - 根据
boost_mode的策略,将BIS与FFS组合,形成最终相关性得分; - 最终的相关性得分与
min_score比较,如果最终得分小于min_score,则该文档不会被返回;
对于score_mode,取值如下:
multiply:将多个函数得分相乘,默认值;sum:将多个函数得分相加;avg:计算多个函数得分的平均值;max:取多个函数得分的最大值;min:取多个函数得分的最小值;first:取第一个函数得分;
对于boost_mode,取值如下:
multiply:将BIS与FFS相乘,默认值;replace:使用FFS替代BIS,最终相关性得分就是FFS;sum:将BIS与FFS相加;max:取BIS与FFS的较大值;min:取BIS与FFS的较小值;
下面来介绍函数,有以下几类函数:
weight:提供一个常量值作为函数得分,例如:
json{ "weight": 10 }random_score:提供一个随机数作为函数得分,随机数的范围在
之间; random_score 得分函数有两种模式:默认模式(高效但不可重现) 和 可重现模式(指定 seed 和 field):
默认模式:
random_score使用 Lucene 内部的 文档 ID (docID) 作为随机数的来源json{ "random_score":{} }可重现模式:提供
seed(种子) 和field(字段) 参数,保证在不同的查询执行中,同一个文档始终获得相同的随机得分。json{ "random_score": { "seed": 10, "field": "_seq_no" } }在这种模式下,最终得分的计算是基于以下几个因素的组合:
seed(种子): 提供的固定数值。只要这个种子不变,得分的基础就不会变。field(字段): 用于生成随机数的文档字段。ES 会使用该文档中此字段的最小值。- _seq_no 字段: 一个很好的默认选择是
_seq_no(序列号)字段,它在每个文档更新时都会改变。缺点是如果文档被更新,其 _seq_no 值会变,导致随机得分也随之改变。
salt(盐值): 一个自动计算的附加值,基于索引名称和分片 ID。- 作用: 确保即使两个文档具有完全相同的 seed 和 field 值,但如果它们存储在不同的索引或分片中,它们仍然会获得不同的随机得分。
- _seq_no 字段: 一个很好的默认选择是
field_value_factor:基于文档字段值获取函数得分,语法如下:
json{ "field_value_factor":{ "field": "my-int", "factor": 1.2, "modifier": "sqrt", "missing": 1 } }上面的例子会通过以下计算公式产生函数得分:
field:文档数字类型字段名称,提供基础得分;factor:可选值,默认值为1,将基础得分乘以factor的值,进一步计算得分;modifier:数学公式用于计算最终得分,默认值为none,取值有none,log,log1p,log2p,ln,ln1p,ln2p,square,sqrt, 或reciprocal;missing:如果文档缺少field指定的字段,那么使用missing提供的默认值,再进行factor和modifier计算;
script_score:使用Painless脚本语言来计算函数得分,是最灵活的方式。
painless脚本语法:https://www.elastic.co/docs/reference/scripting-languages/painless/painless
json{ "script_score": { "script": { "params": { "a": 5, "b": 1.2 }, "source": "params.a / Math.pow(params.b, doc['my-int'].value)" } } }Decay functions:衰减函数,根据文档的某个字段值与一个参考点 (Origin) 之间的距离来计算得分,得分值在
之间。 衰减函数有三种类型:
linear,exp, 或gauss,语法如下:json{ "Decay function":{ "FIELD_NAME": { "origin": "11, 12", "scale": "2km", "offset": "0km", "decay": 0.33 } } }Decay function:衰减函数,linear,exp, 或gauss三种之一;FIELD_NAME:字段名称,字段类型需要为numeric,date, 或geopoint类型;origin:用于计算距离的原点,对于FIELD_NAME为geopoint和numeric类型是必需的,并且值为对应的类型;对于date类型,不是必需的,如果没提供,默认值为now,并且Date math (例如now-1h)也是支持的;offset:可选值,默认值为0,在范围内,函数得分的值与 origin点的得分相同,都为1;scale:必需的,定义得分从最高值衰减到decay阈值所需的距离。换句话说,在点(origin - offset - scale)和(origin+offset+scale)这两个点处,函数得分为decay值;对于
geopoint类型,scale的有效值是数字+距离单位,例如1m,12km,默认距离单位是m;对于
date类型,scale的有效值是数字+时间单位,例如1d,12h,默认时间单位是毫秒ms;对于
numeric,scale的有效值是任何数字;decay:可选的,默认值为0.5,定义了目标衰减值;
下图是衰减函数的示意图:

对于上述的函数,我们还可以定义filter,用于定义该函数可以应用在哪些文档上,只有符合条件的文档才会应用该函数,例如:
json
{
"weight": 100,
"filter": {
"exists": {
"field": "age"
}
}
}表示只有存在age字段的文档,才会应用weight函数计算一个函数得分;
例子:
txt
GET /user/_search
{
"query": {
"function_score": {
"query":{
"term": {
"name": {
"value": "ls"
}
}
},
"functions": [
{
"weight": 100,
"filter": {
"exists": {
"field": "age"
}
}
},
{
"random_score": {
"seed": 10,
"field": "_seq_no"
}
},
{
"field_value_factor": {
"field": "num"
}
}
],
"boost_mode": "replace",
"score_mode": "multiply",
"max_boost": 50,
"min_score": 1
}
}
}5. 其他查询类型
5.1 match_all 与 match_none
match_all用于匹配全部文档,语法如下:
json
{
"match_all":{}
}此时所有的文档得分为1.0,我们可以使用boost改变文档得分:
json
{
"match_all":{
"boost": 2.0
}
}match_none用于不匹配任何文档,语法如下:
json
{
"match_none": {}
}6. 其他查询设置
6.1 排序
在_search API中,默认的查询结果是按照相关性得分排序的,我们可以自定义排序顺序,通过sort数组定义排序规则:
json
{
"query":{},
"sort":[] // 定义排序规则
}sort 参数是一个数组,允许指定一个或多个排序规则。ES 会按照数组中规则的定义顺序依次进行排序。
例如:
json
{
"sort" : [
{ "post_date" : {"order" : "asc"}},
{ "name" : "desc" },
{ "age" : "desc" },
"user",
"_score" // 文档相关性得分
]
}以上格式都是有效的格式。
{ "post_date" : {"order" : "asc"}}:完整形式,以JSON对象的形式指定排序字段和排序方向;{ "name" : "desc" }:简写形式,以字段+排序方向的形式指定排序;"user":默认形式,只指定排序字段,排序方向按照默认方向;
排序方向有两种:asc(升序)和desc(降序);默认情况下,对于_score字段,ES按照降序排序,其他字段则按照升序排序。
为了性能考虑,不要在text类型的字段上进行排序。
如果指定了排序,那么在返回结果中,对于结果文档会多一个字段sort,用来表示排序值,例如:
json
{
"_index" : "user",
"_type" : "_doc",
"_id" : "boBsSpgB9u22YON4d_aR",
"_score" : 1.0,
"_source" : {
"name" : [
"ls",
"ls1",
"ls2",
"ls3"
],
"num" : 20
},
"sort" : [
20
]
}对于日期时间类型,如果未指定format,那么返回的排序值是时间戳,我们可以在指定排序时指定format格式,将内部的时间转换为统一的日期时间格式:
json
{
"timestamp":{
"order":"desc",
"format":"yyyy-MM-dd HH:mm:ss"
}
}如果指定了排序,那么ES将不会计算文档相关性得分,如果想查看文档相关性得分,可以使用track_scores:
json
{
"track_scores": true,
"sort" : [
{ "post_date" : {"order" : "desc"} }
],
"query" : {
"term" : { "user" : "kimchy" }
}
}6.2 分页
在_search API 中,分页通过from和size来实现,例如:
json
{
"query":{
// 查询条件
},
"sort":[],
"from": 0,
"size": 5
}默认情况下,from的值为0,size的值为10,也就是说,不指定from和size参数,那么ES会返回最匹配的10个文档。
ES 限制 from + size的值加起来不允许超过10000,可以通过设置 index.max_result_window 改变该项限制。
如果想要返回所有文档,即不分页,那么有两种方式:search after和scroll。
但是,由于ES不再推荐scroll方式进行深分页。
6.2.1 search after
search_after的实现基于排序,返回的结果文档包含排序字段sort,那么后一页的内容,便可以基于前一页的排序字段值。
换句话说,排序是按照ID升序排列的,前一页最后一条的ID为10,那么要查询后一页的ID值肯定大于10,在 SQL 中的实现类似于:
sql
select *
from tmp_table
where id > 10
order by id asc
limit 10注意,由于限制了 id > 10,所以在limit 时,不用再指定偏移量
在ES中,也可以利用类似的思想,即search_after,步骤如下:
- 首先执行第一次查询,包含自定义排序,即返回第一页内容;
- 记录下返回结果中最后一条的
sort排序值; - 在后续查询中,增加
search_after字段,值就是前一次查询中最后一条的sort值; - 重复执行第2、3步,直至返回结果的
hits为空,即可查询所有的文档;
例子如下:
Details
第一步,查询第一页内容:
txt
GET /user/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"num": {
"order": "desc"
}
}
],
"size": 2
}记录下结果中最后一条记录的sort的值:
json
"sort" : [
20
]然后执行下一次查询,返回后一页的内容:
txt
GET /user/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"num": {
"order": "desc"
}
}
],
"size": 2,
"search_after":[20]
}从上面的例子我们可以看到,单纯使用search_after有以下缺点:
- 如果排序字段不能唯一确定文档顺序,那么可能造成漏文档。例如,我们有10个文档的
num值都是20,第一次返回2个num值为20的文档,第二次使用"search_after":[20]再次查询,会发现结果不再包含num值为20的文档,其他8个num值为20的文档被漏了; - 如果在我们连续使用
search_after期间,索引文档有了更新(增删改),那么可能会造成结果不准确;
为了解决上面的问题,ES提出了PIT(point in time,在ES 7.10.0推出),类似于快照的思想,在执行查询前,先给索引拍一个快照,快照保证在查询期间不会被改变,因此不会担心索引文档更新的问题,并且,PIT还会自动给排序最后增加一个_shard_doc规则,_shard_doc能保证每个文档都有一个唯一值,因此不用担心漏文档的问题。
使用PIT的顺序如下:
首先创建PIT,使用如下API:
txtPOST /index-name/_pit?keep_alive=1m例如:
txtPOST /user/_pit?keep_alive=1m返回结果包含PIT ID:
json{ "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==", }然后执行第一次查询,在PIT的查询中有以下两个注意点:
在查询路径中,不应该包含索引名称,例如:
txtGET /_search在查询体中,应该使用返回的PIT ID:
txtGET /_search { "query": { "match_all": {} }, "sort": [ { "num": { "order": "desc" } } ], "pit":{ "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==", "keep_alive":"1m" }, "size": 2 }
在后续的查询中,在
searc_after中使用前一页最后一条的sort值,并且使用前一次查询返回的pit_id更新请求体中的pit.id:txtGET /_search { "query": { "match_all": {} }, "sort": [ { "num": { "order": "desc" } } ], "pit":{ "id":"o-a1AwEEdXNlchZ0blRUNVlpRVF0cW41cHhpbFhKdXB3ABZ5bGU3MHo1eFRiS3pnZm5tRTI0LXp3AAAAAAAAAw9PFkxmUjktd0dFUnZ5M3hkNzJMa1ZuaUEAARZ0blRUNVlpRVF0cW41cHhpbFhKdXB3AAA=", "keep_alive":"1m" }, "search_after":[20,6], "size": 2 }持续进行查询,直至返回的结果为空,然后,删除PIT:
txtDELETE /_pit { "id" : "o-a1AwEEdXNlchZ0blRUNVlpRVF0cW41cHhpbFhKdXB3ABZ5bGU3MHo1eFRiS3pnZm5tRTI0LXp3AAAAAAAAAw9PFkxmUjktd0dFUnZ5M3hkNzJMa1ZuaUEAARZ0blRUNVlpRVF0cW41cHhpbFhKdXB3AAA=" }
TIP
关于keep_alive的理解
当使用 PIT 时,ES 会创建一个轻量级的资源,本质上是锁定了索引在那一瞬间(那个时间点)的视图,也就是快照。PIT 是一个有成本的资源,它会消耗堆内存和文件描述符。为了防止资源被无限期占用,ES 会为每个 PIT 设置一个生命周期,这就是创建PIT时的请求参数keep_alive。
当后续执行查询时,也会携带一个keep_alive时间,用来更新PIT的存活时间,即重置该 PIT 资源的计时器。
如果设置PIT的keep_alive时间过短,那么ES会删除PIT资源,造成后续的查询失败。
虽然ES使用keep_alive来保证PIT资源可以被删除,但是,在执行完查询后,仍然要记得手动删除PIT资源。
6.2.2 scroll
scroll 同样可以理解为创建索引快照,之后,在该快照基础上,每次批量查询数据,流程如下:
第一次查询时,创建
scroll环境,即在_search查询参数中加上scroll:txtGET /xxx/_search?scoll=1m值为
keep-alive时间;第一次查询结果中会包含
_scroll_id字段,如下:json{ "_scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFkxmUjktd0dFUnZ5M3hkNzJMa1ZuaUEAAAAAAAQBFhZ5bGU3MHo1eFRiS3pnZm5tRTI0LXp3" // 省略其他字段 }然后,使用
/_search/scrollAPI,传入返回的_scroll_id,获取下一批数据:txtPOST /_search/scroll { "scroll" : "1m", "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFkxmUjktd0dFUnZ5M3hkNzJMa1ZuaUEAAAAAAAQBFhZ5bGU3MHo1eFRiS3pnZm5tRTI0LXp3" }注意:路径上没有索引名称。
每次使用
/_scroll/scrollAPI 的结果都会返回_scroll_id,每次获取最新的_scroll_id,然后重复调用_search_scroll,获取下一批数据,直至返回结果为空;最后,删除
scroll环境:txtDELETE /_search/scroll { "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==" }
6.3 返回指定字段
查询返回结果中,除了原始文档(在_source字段中返回),ES还会增加一些元数据字段,例如_id、_index等,针对两种不同字段,有两种方式控制返回字段。
6.3.1 _source
在使用_search API时,我们可以使用_source选项,控制在_source中返回的字段。有以下几种形式:
"_source": false:不返回_source字段,即不返回原始文档:json{ "_source": false, "query": { ... } }"_source": "obj":只返回obj字段:json{ "_source": "obj", "query": { ... } }"_source": ["obj", "geo"]:返回obj和geo字段:json{ "_source": ["obj","geo"], "query": { ... } }以对象的形式指定要返回的字段,以及不返回的字段:
json"_source": { "includes": [ "obj1.*", "obj2.*" ], "excludes": [ "*.description" ] }includes:指定要返回的字段,可以使用通配符;excludes:指定不返回的字段,可以使用通配符;
当只有
includes时,则返回指定的字段;当只有
excludes时,则不返回指定的字段;当同时有
includes和excludes时,先返回includes中指定的字段,然后再从返回的字段中去除excludes中指定的字段;例如:
txtGET /kibana_sample_data_logs/_search { "_source": { "includes": "geo", "excludes": ["*.dest","*.src"] }, "query": { "match_all":{} } }
6.3.2 fields
在使用_source指定返回字段时,如果我们只想返回id字段,_source方式仍然会加载整个原始文档,如果原始文档很大(例如包含一篇文章内容),那么这种方式将会消耗很多资源。
fields指定返回字段,最大的优势在于不会直接从source原始文档中解析字段值,而是会从DocValue和Store Field中高效获取值,避免加载整个原始文档,前两步失败了,才会从source原始文档中获取字段值。
使用fields通常会将 _source 禁用:
txt
GET /user/_search
{
"query": {
"match_all": {}
},
"_source": false,
"fields": [
"name",
"_id"
]
}某个文档示例结果如下:
txt
{
"_index" : "user",
"_type" : "_doc",
"_id" : "b4BuSpgB9u22YON4P_Yo",
"_score" : 1.0,
"fields" : {
"name" : [
"ls"
],
"_id" : [
"b4BuSpgB9u22YON4P_Yo"
]
}
}可以看到,结果字段是以数组的形式返回的。
参考资料
[1] 如何安装示例数据集:https://www.elastic.co/docs/extend/kibana/sample-data
[2] Term-level-query:https://www.elastic.co/docs/reference/query-languages/query-dsl/term-level-queries
[3] painless脚本语言:https://www.elastic.co/docs/reference/scripting-languages/painless/painless
[4] 排序查询结果:https://www.elastic.co/docs/reference/elasticsearch/rest-apis/sort-search-results