Elasticsearch 查询文档

226 阅读1小时+

一:开始搜索

一旦你开始在 Elasticsearch 插入数据,你就可以通过_search 方式发送请求来进行搜索,如果使用匹配搜索功能,在请求体中使用 Elasticsearch Query DSL 指定搜索条件。你也可以在请求头指定要搜索的索引名称。

如下图所示:搜索银行索引中,所有账号按照 account_number 排序:

GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ]
}

默认情况下,会返回符合条件搜索的前 10 个文档:

{
  "took" : 63,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
        "value": 1000,
        "relation": "eq"
    },
    "max_score" : null,
    "hits" : [ {
      "_index" : "bank",
      "_type" : "_doc",
      "_id" : "0",
      "sort": [0],
      "_score" : null,
      "_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
    }, {
      "_index" : "bank",
      "_type" : "_doc",
      "_id" : "1",
      "sort": [1],
      "_score" : null,
      "_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
    }, ...
    ]
  }
}

响应体里还提供了有关搜索请求的相关信息:

  • took – 查询花费时长(毫秒)
  • timed_out – 请求是否超时
  • _shards – 搜索了多少分片,成功、失败或者跳过了多个分片(明细)
  • max_score – 最相关的文档分数
  • hits.total.value - 找到的文档总数
  • hits.sort - 文档排序方式 (如没有则按相关性分数排序)
  • hits._score - 文档的相关性算分 (match_all 没有算分)

每个搜索请求都是独立的:Elasticsearch 不维护任何请求中的状态信息。如果做分页的话,请在请求中指定 From 和 Size 参数。
如图所示:搜索第 10 到 19 的数据

GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ],
  "from": 10,
  "size": 10
}

现在你已经了解如何提交最基本的搜索请求了,则可以开始构建比 match_all 更有趣的查询了。

想搜索特定的字段,可以使用匹配查询。如下所示,请求搜索地址字段以查询地址中包含 mill 或 lane 的客户

GET /bank/_search
{
  "query": { "match": { "address": "mill lane" } }
}

如果要全部匹配而不是仅仅是包含关键字之一,你需要使用 match_phrase 而不是 match。(译者注:match 和 match_phrase 的区别就像是 or 和 and 的区别) 如图所示:在请求中查询地址中同时包含 mill 和 lane 的客户:

GET /bank/_search
{
  "query": { "match_phrase": { "address": "mill lane" } }
}

如果要构造更复杂的查询,可以使用布尔查询来组合多个查询条件,must matchshould matchmust not match

如图所示:请求在银行索引里搜索年龄是 40 的,但不居住在爱达荷州 (ID) 的客户:

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "state": "ID" } }
      ]
    }
  }
}

布尔查询中每个 mustshould,must_not 都被称为查询子句。每个
must 或者 should 查询子句中的条件都会影响文档的相关得分。得分越高,文档跟搜索条件匹配得越好。默认情况下,Elasticsearch 返回的文档会根据相关性算分倒序排列。

must_not 子句中认为是过滤条件。它会过滤返回结果,但不会影响文档的相关性算分,你还可以明确指定任意过滤条件去筛选结构化数据文档。

如图所示:请求搜索余额在 20000 ~ 30000(包括 30000) 之间的账户

GET /bank/_search
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}

二:使用聚合分析

Elasticsearch 聚合让你看到一些有关搜索结果元信息,返回结果能显示例如:“德克萨斯州有多少个开户的人?” 或者 “田纳西州的平均帐户余额是多少?”。你可以搜索文档,过滤 hits,使用聚合去分析并返回结果。

如图所示:请求使用 terms 聚合对 bank 索引中对所有还在账户进行分组,并安装返回前 10 个账户数最多的州:

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      }
    }
  }
}
{
  "took": 29,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped" : 0,
    "failed": 0
  },
  "hits" : {
     "total" : {
        "value": 1000,
        "relation": "eq"
     },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_state" : {
      "doc_count_error_upper_bound": 20,
      "sum_other_doc_count": 770,
      "buckets" : [ {
        "key" : "ID",
        "doc_count" : 27
      }, {
        "key" : "TX",
        "doc_count" : 27
      }, {
        "key" : "AL",
        "doc_count" : 25
      }, {
        "key" : "MD",
        "doc_count" : 25
      }, {
        "key" : "TN",
        "doc_count" : 23
      }, {
        "key" : "MA",
        "doc_count" : 21
      }, {
        "key" : "NC",
        "doc_count" : 21
      }, {
        "key" : "ND",
        "doc_count" : 21
      }, {
        "key" : "ME",
        "doc_count" : 20
      }, {
        "key" : "MO",
        "doc_count" : 20
      } ]
    }
  }
}{
  "took": 29,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped" : 0,
    "failed": 0
  },
  "hits" : {
     "total" : {
        "value": 1000,
        "relation": "eq"
     },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_state" : {
      "doc_count_error_upper_bound": 20,
      "sum_other_doc_count": 770,
      "buckets" : [ {
        "key" : "ID",
        "doc_count" : 27
      }, {
        "key" : "TX",
        "doc_count" : 27
      }, {
        "key" : "AL",
        "doc_count" : 25
      }, {
        "key" : "MD",
        "doc_count" : 25
      }, {
        "key" : "TN",
        "doc_count" : 23
      }, {
        "key" : "MA",
        "doc_count" : 21
      }, {
        "key" : "NC",
        "doc_count" : 21
      }, {
        "key" : "ND",
        "doc_count" : 21
      }, {
        "key" : "ME",
        "doc_count" : 20
      }, {
        "key" : "MO",
        "doc_count" : 20
      } ]
    }
  }
}

如图所示:在返回结果中 buckets 的值是字段 state 的桶。 doc_count 表示每州的账户数。你可以看到爱达荷州中有 27 个帐户,由于 "size": 0 表示了返回只有聚合结果,无具体数据。

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

你可以使用组合聚合来构建更为复杂的统计数据。如图所示:请求将 avg 聚合嵌套在 group_by_state 聚合内,以计算每州的平均账户余额。

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

如图所示:你也可以不按内置结果进行排序,而可以使用聚合后的 average_balance 字段进行排序

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword",
        "order": {
          "average_balance": "desc"
        }
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

除了这些基本的桶跟指标聚合之外,Elasticsearch 还提供了其他类型的聚合,用于对多个字段进行操作和分析特定类型的数据,如日期、IP 和地理数据。你还可以将单个聚合的结果送到管道聚合中进行深一步分析。聚合提供了核心分析功能支持机器学习检测异常等高级功能。

三: Aggregation

3.0 聚合

聚合框架有助于基于搜索查询提供聚合数据。它基于称为聚合的简单构建块,可以进行组合以构建复杂的数据摘要。

聚合可以看作是在一组文档上建立分析信息的工作单元。执行的上下文定义此文档集是什么 (例如,在已执行的搜索请求的查询 / 过滤器的上下文中执行顶级聚合)。

有许多不同类型的聚合,每种聚合都有自己的目的和输出。为了更好地理解这些类型,通常更容易将它们分为四个主要系列:

[Bucketing]

  • 生成存储桶的一组聚合,其中每个存储桶都与一个和一个文档条件相关联。执行聚合时,将对上下文中的每个文档评估所有存储桶条件,并且当条件匹配时,该文档将被视为 “落入” 相关存储桶。在汇总过程结束时,我们将获得一个存储桶列表 - 每个存储桶都带有一组 “属于” 的文档。

[Metric]

  • 用于跟踪和计算一组文档的指标的聚合。

[Matrix]

  • 一组聚合,可在多个字段上进行操作,并根据从请求的文档字段中提取的值生成矩阵结果。与指标和存储桶聚合不同,此聚合系列尚不支持脚本。

[Pipeline]

  • 聚合其他聚合的输出及其相关度量的聚合

接下来是有趣的部分。由于每个存储桶有效地定义了一个文档集 (所有属于该存储桶的文档),可以潜在地关联 bucket 级别上的聚合,这些聚合将在该 bucket 的上下文中执行。这就是聚合的真正力量发挥作用的地方: 聚合可以嵌套!

注意 : 存储桶聚合可以具有子聚合 (存储桶或指标)。子聚合将为其父聚合生成的存储桶进行计算。嵌套聚合的级别 / 深度没有硬性限制 (可以将一个聚合嵌套在 “父” 聚合下,该父聚合本身是另一个更高级别聚合的子聚合)。

注意:聚合对数据的双重表示起作用。因此当在绝对值大于 2 ^ 53 的长度上运行时,结果可能是近似的。

结构聚合

下面的代码片段描述了聚合的基本结构:

"aggregations" : {
    "<aggregation_name>" : {
        "<aggregation_type>" : {
            <aggregation_body>
        }
        [,"meta" : {  [<meta_data_body>] } ]?
        [,"aggregations" : { [<sub_aggregation>]+ } ]?
    }
    [,"<aggregation_name_2>" : { ... } ]*
}

JSON 中的 aggregations 对象(也可以使用 aggs)保存要计算的聚合。每个聚合都与用户定义的逻辑名称相关联(例如,如果聚合计算平均价格,则将其命名为 avg_price 是有意义的)。这些逻辑名称还将用于唯一地标识响应中的聚合。每个聚合都具有特定的类型 (上面的代码片段中为 <aggregation_type>),通常是命名聚合主体中的第一个键。每种聚合类型都取决于聚合的性质 (例如,特定字段上的 avg 聚合将定义要在其上计算平均值的字段),定义自己的主体。在聚合类型定义的同一级别上,可以选择定义一组其他聚合,尽管仅当您定义的聚合具有存储特性时才有意义。在这种情况下,将为存储桶聚合构建的所有存储桶计算您在存储桶聚合级别上定义的子聚合。例如,如果您在 range 聚合下定义一组聚合,则将为定义的范围存储桶计算子聚合。

聚合值的来源

某些聚合对从聚合文档中提取的值起作用。通常这些值将从特定的文档字段中提取,该字段是使用聚合的 field 键设置的。它也可以定义一个 [script] 去生成值 (每个文档)。

如果为聚合配置了 value 和 script 设置,则该脚本将被视为 value script。虽然普通脚本是在文档级别评估的 (即脚本可以访问与文档关联的所有数据),但值脚本是在级别评估的。在这种模式下,将从配置的 field 中提取值,并使用 script 对这些值进行 " 转换 ''。

注意:当使用脚本时,可以定义 lang 和 parmas 设置,前者定义了所使用的脚本语言(假设在 Elasticsearch 中有合适的语言,可以是默认的,也可以是插件)。后者支持将脚本中的所有 “动态” 表达式定义为参数,这使脚本能够在调用之间保持自身的静态 (这将确保在 Elasticsearch 中使用缓存的编译脚本)。

Elasticsearch 使用映射中的字段类型来确定如何运行聚合和格式化响应。但是有两种情况是 Elasticsearch 无法计算出这些信息:未映射的字段(例如,对于跨多个索引的搜索请求,其中只有一些具有字段映射)和纯脚本。在这种情况下,可以使用 value_type 选项给 Elasticsearch 一个提示,接受以下列值:string, long(适用于所有整数类型), double (适用于所有的十进制类型例如 float 或 scaled_float),date, ip 和 boolean.

3.1 指标汇总

这个类型中的聚合基于以某种方式从被聚合的文档中提取的值来计算度量。这些值通常从文档的字段中提取 (使用字段数据),但是也可以使用脚本生成。。

数值指标聚合是一种特殊类型的指标聚合,可输出数值。一些聚合输出单个数值指标 (平均值),称为单值数值指标聚合,其他聚合则生成多个指标 (统计数据),并称为多值数字指标聚合。单值和多值数值度量聚合之间的区别在这些聚合充当某些 bucket 聚合的直接子聚合时发挥了作用(一些桶聚合使您能够根据每个桶中的数值指标对返回的桶进行排序)。

3.1.1 平均聚合

单值指标聚合计算从聚合文档中提取的数值的平均值。这些值可以从文档中的特定数字字段提取,也可以由提供的脚本生成。

假设数据是由学生考试成绩 (0 到 100) 的文档组成,我们可以用以下方法平均他们的分数:

POST /exams/_search?size=0
{
    "aggs" : {
        "avg_grade" : { "avg" : { "field" : "grade" } }
    }
}

以上汇总计算了所有文档的平均等级。聚合类型为 avg 和 field 设置定义了将计算平均值的文档的数字字段。将返回以下内容:

{
    ...
    "aggregations": {
        "avg_grade": {
            "value": 75.0
        }
    }
}

聚合的名称 (上面的 avg_grade) 也是可以从返回的响应中检索聚合结果的键。

脚本

根据脚本计算平均成绩:

POST /exams/_search?size=0
{
    "aggs" : {
        "avg_grade" : {
            "avg" : {
                "script" : {
                    "source" : "doc.grade.value"
                }
            }
        }
    }
}

这将把 script 参数解释使用 painless 脚本语言且为没有脚本参数的 inline 脚本。如要使用存储的脚本,请使用以下语法:

POST /exams/_search?size=0
{
    "aggs" : {
        "avg_grade" : {
            "avg" : {
                "script" : {
                    "id": "my_script",
                    "params": {
                        "field": "grade"
                    }
                }
            }
        }
    }
}

脚本值

事实证明,该考试远远超出了学生的水平,因此需要进行成绩更正。我们可以使用脚本值来获取新的平均值::

POST /exams/_search?size=0
{
    "aggs" : {
        "avg_corrected_grade" : {
            "avg" : {
                "field" : "grade",
                "script" : {
                    "lang": "painless",
                    "source": "_value * params.correction",
                    "params" : {
                        "correction" : 1.2
                    }
                }
            }
        }
    }
}

缺失值

missing 参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。

POST /exams/_search?size=0
{
    "aggs" : {
        "grade_avg" : {
            "avg" : {
                "field" : "grade",
                "missing": 10 
            }
        }
    }
}
  • 在等级字段中没有值的文档将与值为 10 的文档归入同一存储桶

3.1.2.加权平均聚合

单值度量聚合,计算从聚合文档中提取的数值的加权平均值。这些值可以从文档中的特定数字字段中提取。

在计算常规平均值时,每个数据点都具有相等的权重... 它对最终值的贡献均等。另一方面,加权平均对每个数据点的加权不同。每个数据点对最终值的贡献量是从文档中提取的,或由脚本提供的。

作为一个公式,加权平均数是: ∑(value * weight) / ∑(weight)

一个普通的平均值可以被认为是一个加权平均值,只是其中每个值都有一个隐含权值为 1。

表 3. 加权平均 参数

参数名描述是否必填默认值
value提供值的字段或脚本的配置必填
weight提供权重的字段或脚本的配置必填
format数字响应格式化可选
value_type有关纯脚本或未映射字段的值的提示可选

权重对象有每个字段特定的配置:

表 4. value 参数设置

参数名描述是否必填默认值
field应该从中提取值的字段必填
missing如果字段完全丢失可使用一个值选填

表 5. weight 参数设置

参数名描述是否必填默认值
field应该从中提取值的字段必填
missing如果字段完全丢失可使用一个值选填

例子

如果我们的文档有一个等级字段,该字段的得分为 0-100,而一个 weight 字段,该字段的得分为任意数值,则我们可以使用:

POST /exams/_search
{
    "size": 0,
    "aggs" : {
        "weighted_grade": {
            "weighted_avg": {
                "value": {
                    "field": "grade"
                },
                "weight": {
                    "field": "weight"
                }
            }
        }
    }
}

得到的响应如下:

{
    ...
    "aggregations": {
        "weighted_grade": {
            "value": 70.0
        }
    }
}

虽然每个字段允许多个值,但仅允许一个权重。如果汇总中遇到的文件的权重超过一个 (例如,权重字段是多值字段),它将引发异常。如果遇到这种情况,则需要为权重字段指定脚本,然后使用脚本将多个值组合为一个要使用的值。

该单个权重将独立应用于从 value 字段提取的每个值。

此示例说明如何使用单个权重对具有多个值的单个文档进行平均:

POST /exams/_doc?refresh
{
    "grade": [1, 2, 3],
    "weight": 2
}

POST /exams/_search
{
    "size": 0,
    "aggs" : {
        "weighted_grade": {
            "weighted_avg": {
                "value": {
                    "field": "grade"
                },
                "weight": {
                    "field": "weight"
                }
            }
        }
    }
}

三个值(12 和 3)将作为独立值包括在内,它们的权重均为 2

{
    ...
    "aggregations": {
        "weighted_grade": {
            "value": 2.0
        }
    }
}

聚合结果返回 2.0,与我们手工计算时的预期相符: ((1*2) + (2*2) + (3*2)) / (2+2+2) == 2

虽然每个字段允许多个值,但仅允许一个权重。如果汇总中遇到的文件的权重超过一个 (例如,权重字段是多值字段),它将引发异常。如果遇到这种情况,则需要为权重字段指定脚本,然后使用脚本将多个值组合为一个要使用的值。

该单个权重将独立应用于从 value 字段提取的每个值。

此示例说明如何使用单个权重对具有多个值的单个文档进行平均:

POST /exams/_doc?refresh
{
    "grade": [1, 2, 3],
    "weight": 2
}

POST /exams/_search
{
    "size": 0,
    "aggs" : {
        "weighted_grade": {
            "weighted_avg": {
                "value": {
                    "field": "grade"
                },
                "weight": {
                    "field": "weight"
                }
            }
        }
    }
}

三个值(12 和 3)将作为独立值包括在内,它们的权重均为 2

{
    ...
    "aggregations": {
        "weighted_grade": {
            "value": 2.0
        }
    }
}

聚合结果返回 2.0,与我们手工计算时的预期相符: ((1*2) + (2*2) + (3*2)) / (2+2+2) == 2

脚本

值和权重都可以从脚本而不是字段派生获取。作为一个简单的例子,下面将使用脚本为文档中的等级和权重添加一个:

POST /exams/_search
{
    "size": 0,
    "aggs" : {
        "weighted_grade": {
            "weighted_avg": {
                "value": {
                    "script": "doc.grade.value + 1"
                },
                "weight": {
                    "script": "doc.weight.value + 1"
                }
            }
        }
    }
}

缺失值

 missing 参数定义了当文档缺失默认值的时候应如何处理。 value 跟 weight 的默认行为是不同的:

默认情况下,如果缺少 value 字段,则会忽略该文档,并且聚合将继续到下一个文档。如果缺少 weight 字段,则假定其具有 1 的权重 (如正常平均值)。

可以使用 missing 参数覆盖这两个默认值:

POST /exams/_search
{
    "size": 0,
    "aggs" : {
        "weighted_grade": {
            "weighted_avg": {
                "value": {
                    "field": "grade",
                    "missing": 2
                },
                "weight": {
                    "field": "weight",
                    "missing": 3
                }
            }
        }
    }
}

3.1.3 基数聚合

单指标聚合,可以计算不同值的近似计数,值可以从文档中的特定字段提取,也可以由脚本生成。

假设您正在为商店的销售建立索引,并希望计算与查询匹配的已售产品唯一数量:

POST /sales/_search?size=0
{
    "aggs" : {
        "type_count" : {
            "cardinality" : {
                "field" : "type"
            }
        }
    }
}

响应:

{
    ...
    "aggregations" : {
        "type_count" : {
            "value" : 3
        }
    }
}

精准控制

该聚合还支持 precision_threshold 选项:

POST /sales/_search?size=0
{
    "aggs" : {
        "type_count" : {
            "cardinality" : {
                "field" : "type",
                "precision_threshold": 100 
            }
        }
    }
}
  • precision_threshold 选项允许以内存交换精度,并定义了一个唯一的计数,低于该值时,期望计数将接近于准确。高于该值时,计数可能会变得模糊(误差变大)。最大计数是 40000,高于这个阈值跟 40000 效果一样,默认值是 30000。

计数是近似值

计算精确计数需要将值加载到哈希集中并返回其大小。当处理高基数集较大的值时,这不会自动扩容,所以所需的内存使用情况以及在节点之间传递每个分片集的需求会占用过多的群集资源。

 cardinality 聚合是基于  HyperLogLog++ 算法,基于具有一些有趣属性的值的哈希进行计数

  • 可配置的精度,该精度决定了如何以内存换取精度
  • 在低基数集上具有出色的准确性
  • 固定的内存使用量:无论是否有成百上千的唯一值,内存使用量仅取决于配置的精度。

对于 c 的精度阈值,我们正在使用的实现大约需要 c * 8 个字节。

下表显示了阈值前后误差如何变化:

5.1.3. Cardinality Aggregation

对于所有 3 个阈值,计数已精确到配置的阈值。虽然不能保证,但很可能是这样。实际的准确性取决于所讨论的数据集。一般来说,大多数数据集都显示出一贯良好的准确性。还要注意,即使阈值低至 100,在计算数百万个项目时,错误仍然非常低 (如上图所示为 1-6%)。

HyperLogLog++ 算法依赖于散列值的前导零,数据集中散列的精确分布会影响基数的准确性。

请注意,即使阈值低至 100,错误仍然很低,即使在计算数百万项时也是如此。

预计算哈希

在具有高基数的字符串字段上,将字段值的哈希存储在索引中然后在此字段上运行基数聚合可能会更快。这可以通过从客户端提供哈希值来完成,也可以让 Elasticsearch 通过使用 mapper-murmur3 插件完成。

注意: 预计算哈希通常仅在非常大或高基数的字段上有用,因为它可以节省 CPU 和内存。但是,在数字字段上哈希运算非常快,存储原始值所需的内存与存储哈希值所需的内存相同或更少。对于低基数的字符串字段也是如此,特别是考虑到这些字段已进行了优化,以确保每个段的每个唯一值最多计算一次哈希。

脚本

 cardinality 指标支持脚本编写,但是由于需要实时计算哈希值,因此性能受到了明显的影响:

POST /sales/_search?size=0
{
    "aggs" : {
        "type_promoted_count" : {
            "cardinality" : {
                "script": {
                    "lang": "painless",
                    "source": "doc['type'].value + ' ' + doc['promoted'].value"
                }
            }
        }
    }
}

这会将 scrpit 参数解释为具有 painless 脚本语言且没有脚本参数的 inline 脚本。要使用存储的脚本,请使用以下语法:

POST /sales/_search?size=0
{
    "aggs" : {
        "type_promoted_count" : {
            "cardinality" : {
                "script" : {
                    "id": "my_script",
                    "params": {
                        "type_field": "type",
                        "promoted_field": "promoted"
                    }
                }
            }
        }
    }
}

缺失值

missing 参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为有默认值。

POST /sales/_search?size=0
{
    "aggs" : {
        "tag_cardinality" : {
            "cardinality" : {
                "field" : "tag",
                "missing": "N/A" 
            }
        }
    }
}
  • 标签字段中没有值的文档将与值是 N / A 的文档归入同一存储桶

3.1.4 扩展统计聚合

多值度量聚合,用于根据从聚合文档中提取的数值计算统计信息。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。

extended_stats 聚合是统计聚合的扩展版本,其中添加了其他指标,例如 sum_of_squaresvariancestd_deviation 和 std_deviation_bounds

假设数据由代表学生成绩(0~100)的文档组成:

GET /exams/_search
{
    "size": 0,
    "aggs" : {
        "grades_stats" : { 
            "extended_stats" :{
            "field" : "grade" 
            }
        }
    }
}

上面的汇总计算了所有文档的成绩统计。聚合类型为 extended_stats,并且 field 设置定义将要计算统计信息的文档的数字字段。
上面将返回以下内容:

{
    ...

    "aggregations": {
        "grades_stats": {
           "count": 2,
           "min": 50.0,
           "max": 100.0,
           "avg": 75.0,
           "sum": 150.0,
           "sum_of_squares": 12500.0,
           "variance": 625.0,
           "std_deviation": 25.0,
           "std_deviation_bounds": {
            "upper": 125.0,
            "lower": 25.0
           }
        }
    }
}

聚合的名称 (上面的 grades_stats) 也是可以从返回的响应中检索聚合结果的键。

标准偏差范围

默认情况下 extended_stats 指标将返回一个名为 std_deviation_bounds 的对象,该对象提供了平均值的正负两个标准偏差的间隔。这可能是可视化数据差异的有用一种方法。如果要使用其他边界,例如三个标准偏差,则可以在请求中设置 sigma

GET /exams/_search
{
    "size": 0,
    "aggs" : {
        "grades_stats" : {
            "extended_stats" : {
                "field" : "grade",
                "sigma" : 3 
            }
        }
    }
}
  1. sigma 控制应该显示离均值的标准偏差的数量

注意准偏差和界限必须设置正常

默认情况下会显示标准差及其范围,但它们并不总是适用于所有数据集。您的数据必须是正态分布的,这样度量才有意义。标准差背后的统计假设数据是正态分布的,因此,如果数据向左或向右严重倾斜,则返回的值将产生误导。

脚本

根据脚本生成成绩统计:

GET /exams/_search
{
    "size": 0,
    "aggs" : {
        "grades_stats" : {
            "extended_stats" : {
                "script" : {
                    "source" : "doc['grade'].value",
                    "lang" : "painless"
                 }
             }
         }
    }
}

这将把脚本参数解释为单的脚本语言且没有脚本参数的内联脚本。要使用存储的脚本,请使用以下语法:

GET /exams/_search
{
    "size": 0,
    "aggs" : {
        "grades_stats" : {
            "extended_stats" : {
                "script" : {
                    "id": "my_script",
                    "params": {
                        "field": "grade"
                    }
                }
            }
        }
    }
}

值脚本

事实证明,该考试远远超出了学生的水平,因此需要进行成绩更正。我们可以使用值脚本来获取新的统计信息:

GET /exams/_search
{
    "size": 0,
    "aggs" : {
        "grades_stats" : {
            "extended_stats" : {
                "field" : "grade",
                "script" : {
                    "lang" : "painless",
                    "source": "_value * params.correction",
                    "params" : {
                        "correction" : 1.2
                    }
                }
            }
        }
    }
}

缺失值

missing 参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为有默认值

GET /exams/_search
{
    "size": 0,
    "aggs" : {
        "grades_stats" : {
            "extended_stats" : {
                "field" : "grade",
                "missing": 0 
            }
        }
    }
}
  1. 标签 grade 字段中没有值的文档将与值为 0 的文档归入同一存储桶

3.1.5地理边界聚合

计算包含字段的所有 geo_point 值的边界框的度量聚合。

示例:

PUT /museums
{
    "mappings": {
        "properties": {
            "location": {
                "type": "geo_point"
            }
        }
    }
}

POST /museums/_bulk?refresh
{"index":{"_id":1}}
{"location": "52.374081,4.912350", "name": "NEMO Science Museum"}
{"index":{"_id":2}}
{"location": "52.369219,4.901618", "name": "Museum Het Rembrandthuis"}
{"index":{"_id":3}}
{"location": "52.371667,4.914722", "name": "Nederlands Scheepvaartmuseum"}
{"index":{"_id":4}}
{"location": "51.222900,4.405200", "name": "Letterenhuis"}
{"index":{"_id":5}}
{"location": "48.861111,2.336389", "name": "Musée du Louvre"}
{"index":{"_id":6}}
{"location": "48.860000,2.327000", "name": "Musée d'Orsay"}

POST /museums/_search?size=0
{
    "query" : {
        "match" : { "name" : "musée" }
    },
    "aggs" : {
        "viewport" : {
            "geo_bounds" : {
                "field" : "location", 
                "wrap_longitude" : true 
            }
        }
    }
}
  1. geo_bounds 聚合指定用于获取边界的字段
  2. wrap_longitude 是一个可选参数,指定是否允许边界框与国际日期行重叠。默认值是 true

上面的聚合演示了如何计算具有业务类型 shop 的所有文档的 location 字段的边界框

上面的聚合的响应:

{
    ...
    "aggregations": {
        "viewport": {
            "bounds": {
                "top_left": {
                    "lat": 48.86111099738628,
                    "lon": 2.3269999679178
                },
                "bottom_right": {
                    "lat": 48.85999997612089,
                    "lon": 2.3363889567553997
                }
            }
        }
    }
}

3.1.6地理重心聚合

有一种聚合,它根据地理点的所有坐标值计算加权的矩心字段。

示例:

PUT /museums
{
    "mappings": {
        "properties": {
            "location": {
                "type": "geo_point"
            }
        }
    }
}

POST /museums/_bulk?refresh
{"index":{"_id":1}}
{"location": "52.374081,4.912350", "city": "Amsterdam", "name": "NEMO Science Museum"}
{"index":{"_id":2}}
{"location": "52.369219,4.901618", "city": "Amsterdam", "name": "Museum Het Rembrandthuis"}
{"index":{"_id":3}}
{"location": "52.371667,4.914722", "city": "Amsterdam", "name": "Nederlands Scheepvaartmuseum"}
{"index":{"_id":4}}
{"location": "51.222900,4.405200", "city": "Antwerp", "name": "Letterenhuis"}
{"index":{"_id":5}}
{"location": "48.861111,2.336389", "city": "Paris", "name": "Musée du Louvre"}
{"index":{"_id":6}}
{"location": "48.860000,2.327000", "city": "Paris", "name": "Musée d'Orsay"}

POST /museums/_search?size=0
{
    "aggs" : {
        "centroid" : {
            "geo_centroid" : {
                "field" : "location" 
            }
        }
    }
}
  1. geo_centroid 聚合指定用于计算质心的字段。 (注意:字段必须为地理点类型)

上面的汇总显示了如何计算所有犯罪类型盗窃文件的位置字段的质心

以上汇总的响应:

{
    ...
    "aggregations": {
        "centroid": {
            "location": {
                "lat": 51.00982965203002,
                "lon": 3.9662131341174245
            },
            "count": 6
        }
    }
}

将 geo_centroid 聚合作为子聚合合并到其他存储桶聚合时会更加有趣。
例如:

POST /museums/_search?size=0
{
    "aggs" : {
        "cities" : {
            "terms" : { "field" : "city.keyword" },
            "aggs" : {
                "centroid" : {
                    "geo_centroid" : { "field" : "location" }
                }
            }
        }
    }
}

上面的示例使用 geo_centroid 作为 terms 的子聚合存储桶聚合,用于查找每个城市的博物馆的中心位置。

以上汇总的响应:

{
    ...
    "aggregations": {
        "cities": {
            "sum_other_doc_count": 0,
            "doc_count_error_upper_bound": 0,
            "buckets": [
               {
                   "key": "Amsterdam",
                   "doc_count": 3,
                   "centroid": {
                      "location": {
                         "lat": 52.371655656024814,
                         "lon": 4.909563297405839
                      },
                      "count": 3
                   }
               },
               {
                   "key": "Paris",
                   "doc_count": 2,
                   "centroid": {
                      "location": {
                         "lat": 48.86055548675358,
                         "lon": 2.3316944623366
                      },
                      "count": 2
                   }
                },
                {
                    "key": "Antwerp",
                    "doc_count": 1,
                    "centroid": {
                       "location": {
                          "lat": 51.22289997059852,
                          "lon": 4.40519998781383
                       },
                       "count": 1
                    }
                 }
            ]
        }
    }
}

3.1.7 最大聚合

单值度量聚合,可跟踪并返回从聚合文档中提取的数值中的最大值。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。

注意min 和 max 聚合表示对 double 类型的数据进行操作,结果,当在绝对值大于 2 ^ 53 的多头上运行时,结果可能是近似的。

计算所有单据的最大价格值

POST /sales/_search?size=0
{
    "aggs" : {
        "max_price" : { "max" : { "field" : "price" } }
    }
}

相应:

{
    ...
    "aggregations": {
        "max_price": {
            "value": 200.0
        }
    }
}

可以看出,聚合的名称 (上面的 max_price) 也用作键,通过它可以从返回的响应中检索聚合结果。

脚本

max 聚合也可以计算脚本的最大值。下面的示例计算最高价格:

POST /sales/_search
{
    "aggs" : {
        "max_price" : {
            "max" : {
                "script" : {
                    "source" : "doc.price.value"
                }
            }
        }
    }
}

这将使用 Painless 脚本语言,并且不使用脚本参数。要使用存储的脚本,请使用以下语法:

POST /sales/_search
{
    "aggs" : {
        "max_price" : {
            "max" : {
                "script" : {
                    "id": "my_script",
                    "params": {
                        "field": "price"
                    }
                }
            }
        }
    }
}

值脚本

假设我们索引中的文档价格以美元为单位,但是我们想以欧元为单位计算最大值 (在本例中,转换率为 1.2)。我们可以使用值脚本将转化率应用于每个值,然后再进行汇总:

POST /sales/_search
{
    "aggs" : {
        "max_price_in_euros" : {
            "max" : {
                "field" : "price",
                "script" : {
                    "source" : "_value * params.conversion_rate",
                    "params" : {
                        "conversion_rate" : 1.2
                    }
                }
            }
        }
    }
}

缺失值

missing 参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。

POST /sales/_search
{
    "aggs" : {
        "grade_max" : {
            "max" : {
                "field" : "grade",
                "missing": 10 
            }
        }
    }
}

3.1.8. 最小值聚合

单值度量聚合,可跟踪并返回从聚合文档中提取的数值中的最小值。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。

注意:min 和 max 聚合表示对 double 的数据进行操作,结果当在绝对值大于 2 ^ 53 的多头上运行时,结果可能是近似的。

计算所有文档的最低价格值:

POST /sales/_search?size=0
{
    "aggs" : {
        "min_price" : { "min" : { "field" : "price" } }
    }
}

相应:

{
    ...

    "aggregations": {
        "min_price": {
            "value": 10.0
        }
    }
}

可以看出,聚合的名称 (上面的 min_price) 也用作键,通过该键可以从返回的响应中检索聚合结果。

脚本

min 聚合也可以计算脚本的最小值。下面的示例计算最低价格:

POST /sales/_search
{
    "aggs" : {
        "min_price" : {
            "min" : {
                "script" : {
                    "source" : "doc.price.value"
                }
            }
        }
    }
}

这将使用 Painless 脚本语言,并且不使用脚本参数。要使用存储的脚本,请使用以下语法:

POST /sales/_search
{
    "aggs" : {
        "min_price" : {
            "min" : {
                "script" : {
                    "id": "my_script",
                    "params": {
                        "field": "price"
                    }
                }
            }
        }
    }
}

值脚本

假设我们索引中的文档价格以美元为单位,但是我们想以欧元为单位计算最小值 (在本例中,转化率为 1.2)。我们可以使用值脚本将转化率应用于每个值,然后再进行汇总:

POST /sales/_search
{
    "aggs" : {
        "min_price_in_euros" : {
            "min" : {
                "field" : "price",
                "script" : {
                    "source" : "_value * params.conversion_rate",
                    "params" : {
                        "conversion_rate" : 1.2
                    }
                }
            }
        }
    }
}

缺失值

missing 参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。

POST /sales/_search
{
    "aggs" : {
        "grade_min" : {
            "min" : {
                "field" : "grade",
                "missing": 10 
            }
        }
    }
}

3.1.9.百分位数聚合

多值度量标准聚合,可对从聚合文档中提取的数值计算一个或多个百分位数。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。

百分位数表示观察值出现一定百分比的点。例如,第 95 个百分位数是大于观察值的 95%的值。

百分位数通常用于查找异常值。在正态分布中,第 0.13 和第 99.87 个百分位数代表与平均值的三个标准差。任何超出三个标准偏差的数据通常被视为异常。

当检索到一定范围的百分位数时,它们可以用于估计数据分布并确定数据是否偏斜,双峰等。

假设您的数据包含网站加载时间。对于管理员来说,平均加载时间和中值加载时间并不是特别有用。最大值可能变的很出乎意料,它很容易被单一的缓慢响应所扭曲。

让我们看一下代表加载时间的百分比范围:

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_outlier" : {
            "percentiles" : {
                "field" : "load_time" 
            }
        }
    }
}
  1. load_time 字段类型必须是数字

默认情况下,百分比度量标准将生成一定范围的百分比:[1,5,25,50,75,95,99]。响应将如下所示:

{
    ...

   "aggregations": {
      "load_time_outlier": {
         "values" : {
            "1.0": 5.0,
            "5.0": 25.0,
            "25.0": 165.0,
            "50.0": 445.0,
            "75.0": 725.0,
            "95.0": 945.0,
            "99.0": 985.0
         }
      }
   }
}

如您所见,聚合将返回默认范围内每个百分位的计算值。如果我们假设响应时间以毫秒为单位,那么很明显,该网页通常会在 10-725ms 内加载,但偶尔会激增至 945-985ms。

通常,管理员只对异常值 (极端百分比) 感兴趣。我们可以只指定我们感兴趣的百分比 (请求的百分比必须是介于 0 到 100 之间的一个值):

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_outlier" : {
            "percentiles" : {
                "field" : "load_time",
                "percents" : [95, 99, 99.9] 
            }
        }
    }
}
  1. 使用 percents 参数指定要计算的特定百分位数

Keyed 控制

默认情况下,keyed 标志设置为 true,该标志将唯一的字符串键与每个存储桶相关联,并将范围作为哈希而不是数组返回。将 keyed 标志设置为 false 将禁用此行为:

GET latency/_search
{
    "size": 0,
    "aggs": {
        "load_time_outlier": {
            "percentiles": {
                "field": "load_time",
                "keyed": false
            }
        }
    }
}

响应:

{
    ...

    "aggregations": {
        "load_time_outlier": {
            "values": [
                {
                    "key": 1.0,
                    "value": 5.0
                },
                {
                    "key": 5.0,
                    "value": 25.0
                },
                {
                    "key": 25.0,
                    "value": 165.0
                },
                {
                    "key": 50.0,
                    "value": 445.0
                },
                {
                    "key": 75.0,
                    "value": 725.0
                },
                {
                    "key": 95.0,
                    "value": 945.0
                },
                {
                    "key": 99.0,
                    "value": 985.0
                }
            ]
        }
    }
}

脚本

百分位数指标支持脚本。例如,如果我们的加载时间以毫秒为单位,但是我们希望以秒为单位计算百分位数,则可以使用脚本即时转换它们:

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_outlier" : {
            "percentiles" : {
                "script" : {
                    "lang": "painless",
                    "source": "doc['load_time'].value / params.timeUnit", 
                    "params" : {
                        "timeUnit" : 1000   
                    }
                }
            }
        }
    }
}
  1. 用脚本参数替换 field 参数,该脚本参数使用脚本生成在其上计算百分位数的值
  2. 脚本支持参数化输入,就像其他任何脚本一样
GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_outlier" : {
            "percentiles" : {
                "script" : {
                    "id": "my_script",
                    "params": {
                        "field": "load_time"
                    }
                }
            }
        }
    }
}

百分位数 (通常) 是近似值

有许多不同的算法可以计算百分位数。理想中实现只是将所有值存储在一个排序数组中。即要找到第 50 个百分位数,您只需找到 my_array [count(my_array)* 0.5] 的值即可。

显然,理想中的实现是不可伸缩的 — 排序后的数组随数据集中值的数量线性增长。为了计算一个 Elasticsearch 集群中可能有数十亿个值的百分位数,需要计算近似百分位数。

百分位数度量使用的算法称为 TDigest (由 Ted Dunning 引入在 Computing Accurate Quantiles using T-Diges...)

使用此指标时,需要牢记一些准则:

  • 准确度与 q(1-q) 成正比。这意味着极端百分位数 (例如 99%) 比不那么极端百分位数 (例如中位数) 更准确
  • 对于较小的一组值,百分位数非常准确 (如果数据足够小,则可能为 100%准确)。
  • 随着存储桶中值数量的增加,算法开始近似百分位数。它有效地交易了准确性以节省内存。确切的准确度级别很难一概而论,因为它取决于您的数据分布和要聚合的数据量

下表显示了一个均匀分布的相对误差,它取决于收集值的数量和请求的百分位数:

它说明了对于极端百分位数来说,精度是如何更好。对于大量的值,误差减小的原因是大数定律使得值的分布越来越均匀,t-digest 树可以更好地对其进行汇总。对于更倾斜的分布,则不会出现这种情况。

压缩

近似算法必须在存储器利用率与估计精度之间取得平衡。可以使用 compression 参数来控制:

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_outlier" : {
            "percentiles" : {
                "field" : "load_time",
                "tdigest": {
                  "compression" : 200 
                }
            }
        }
    }
}

压缩控制内存使用和近似误差 TDigest 算法使用多个” 节点” 来近似百分位数 - 可用节点越多,与数据量成正比的精度 (以及较大的内存占用量) 就越高。compression 参数将最大节点数限制为 20 * compression。

因此,通过增加压缩值,可以以更多内存为代价来提高百分位数的准确性。较大的压缩值也会使算法变慢,因为基础树数据结构的大小会增加,从而导致操作成本更高。默认压缩值为 100。

一个” 节点” 大约使用 32 个字节的内存,因此在最坏的情况下 (按顺序到达并排序的大量数据),默认设置将产生大约 64KB 的 TDigest 大小。实际上,数据趋向于更加随机,并且 TDigest 将使用更少的内存。

HDR 直方图

注意: 此设置公开了 HDR 直方图的内部实现,语法将来可能会更改。

HDR 直方图 (高动态范围直方图) 是一种替代实现,在计算延迟测量的百分位数时会很有用,因为它比使用 t-digest 实现的速度更快在更大的内存占用量之间进行权衡。此实现维护一个固定的最坏情况百分比错误 (指定为多个有效数字)。这意味着,如果在设置为 3 个有效数字的直方图中以 1 微秒至 1 小时 (3,600,000,000 微秒) 的值记录数据,则对于 1 毫秒和 3.6 秒 (或更佳) 的值,它将保持 1 微秒的值分辨率) 以获取最大跟踪值 (1 小时)。

可以通过在请求中指定 method 参数来使用 HDR 直方图:

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_outlier" : {
            "percentiles" : {
                "field" : "load_time",
                "percents" : [95, 99, 99.9],
                "hdr": { 
                  "number_of_significant_value_digits" : 3 
                }
            }
        }
    }
}
  1. hdr 对象指示应使用 HDR 直方图来计算百分位数,并且可以在对象内部指定此算法的特定设置

  2. number_of_significant_value_digits 指定有效位数的直方图值的分辨率

HDRHistogram 仅支持正值,如果传递负值,则将出错。如果值的范围未知,则使用 HDRHistogram 也不是一个好主意,因为这可能会导致大量内存使用。

缺失值

missing 参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "grade_percentiles" : {
            "percentiles" : {
                "field" : "grade",
                "missing": 10 
            }
        }
    }
}

3.1.10百分数排名聚合

一种多值度量标准聚合,该聚合针对从聚合文档中提取的数值计算一个或多个百分数等级。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。

注意:请参阅 (通常) 百分位数和压缩以获取有关百分位数秩聚合的近似值和内存使用的建议

百分数等级显示低于特定值的观察值的百分比。例如,如果某个值大于或等于观察值的 95%,则它被称为第 95 个百分位等级。

假设您的数据包含网站加载时间。您可能有一项服务协议,其中 95%的页面加载在 500ms 内完成,而 99%的页面加载在 600ms 内完成。

让我们看一下代表加载时间的百分比范围:

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_ranks" : {
            "percentile_ranks" : {
                "field" : "load_time", 
                "values" : [500, 600]
            }
        }
    }
}
  1. load_time 字段必须为数字字段

响应将如下所示:

{
    ...

   "aggregations": {
      "load_time_ranks": {
         "values" : {
            "500.0": 55.00000000000001,
            "600.0": 64.0
         }
      }
   }
}

根据此信息,您可以确定达到了 99%的 600ms 加载时间目标,但还没有达到 95% 500ms 的加载时间目标

Keyed 响应

默认情况下,keyed 标志设置为 true,将唯一的字符串键与每个存储桶相关联,并将范围作为哈希而不是数组返回。将 keyed 标志设置为 false 将禁用此行为:

GET latency/_search
{
    "size": 0,
    "aggs": {
        "load_time_ranks": {
            "percentile_ranks": {
                "field": "load_time",
                "values": [500, 600],
                "keyed": false
            }
        }
    }
}

相应:

{
    ...

    "aggregations": {
        "load_time_ranks": {
            "values": [
                {
                    "key": 500.0,
                    "value": 55.00000000000001
                },
                {
                    "key": 600.0,
                    "value": 64.0
                }
            ]
        }
    }
}

脚本

百分数等级度量标准支持脚本编写。例如,如果我们的加载时间以毫秒为单位,但是我们想以秒为单位指定值,则可以使用脚本即时转换它们:

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_ranks" : {
            "percentile_ranks" : {
                "values" : [500, 600],
                "script" : {
                    "lang": "painless",
                    "source": "doc['load_time'].value / params.timeUnit", 
                    "params" : {
                        "timeUnit" : 1000   
                    }
                }
            }
        }
    }
}
  1. 将 field 参数替换为 script 参数,该参数使用脚本生成在其上计算百分等级的值
  2. 脚本支持参数化输入,就像其他任何脚本一样

这会将 script 参数解释为具有 painless 脚本语言且没有脚本参数的内联脚本。要使用存储的脚本,请使用以下语法:

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_ranks" : {
            "percentile_ranks" : {
                "values" : [500, 600],
                "script" : {
                    "id": "my_script",
                    "params": {
                        "field": "load_time"
                    }
                }
            }
        }
    }
}

HDR 直方图

此设置公开了 HDR 直方图的内部实现,语法将来可能会更改。

HDR 直方图 (高动态范围直方图) 是一种替代实现,在计算延迟测量的百分位等级时可能会很有用,因为它可以比使用 t-digest 实现更快更大内存占用的权衡。此实现维护一个固定的最坏情况百分比错误 (指定为多个有效数字)。这意味着,如果在设置为 3 个有效数字的直方图中以 1 微秒至 1 小时 (3,600,000,000 微秒) 的值记录数据,则对于 1 毫秒和 3.6 秒 (或更佳) 的值,它将保持 1 微秒的值分辨率) 以获取最大跟踪值 (1 小时)。
可以通过在请求中指定方法参数来使用 HDR 直方图:

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_ranks" : {
            "percentile_ranks" : {
                "field" : "load_time",
                "values" : [500, 600],
                "hdr": { 
                  "number_of_significant_value_digits" : 3 
                }
            }
        }
    }
}
  1. hdr 对象指示应使用 HDR 直方图来计算百分位数,并且可以在对象内部指定此算法的特定设置
  1. number_of_significant_value_digits s 指定有效位数的直方图值的分辨率

HDRHistogram 仅支持正值,如果传递负值,则将出错。如果值的范围未知,则使用 HDRHistogram 也不是一个好主意,因为这可能会导致大量内存使用。

缺失值

missing 参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_ranks" : {
            "percentile_ranks" : {
                "field" : "load_time",
                "values" : [500, 600],
                "missing": 10 
            }
        }
    }
}

3.1.11 脚本式指标聚合

使用脚本执行以提供度量标准输出的度量标准聚合。

例子:

POST ledger/_search?size=0
{
    "query" : {
        "match_all" : {}
    },
    "aggs": {
        "profit": {
            "scripted_metric": {
                "init_script" : "state.transactions = []", 
                "map_script" : "state.transactions.add(doc.type.value == 'sale' ? doc.amount.value : -1 * doc.amount.value)",
                "combine_script" : "double profit = 0; for (t in state.transactions) { profit += t } return profit",
                "reduce_script" : "double profit = 0; for (a in states) { profit += a } return profit"
            }
        }
    }
}
  1. init_script 是可选参数,所有其他脚本都是必需的。

以上聚合演示了如何使用脚本汇总来计算销售和成本交易的总利润。

以上聚合的响应:

{
    "took": 218,
    ...
    "aggregations": {
        "profit": {
            "value": 240.0
        }
   }
}

也可以使用存储的脚本指定上述示例,如下所示:

POST ledger/_search?size=0
{
    "aggs": {
        "profit": {
            "scripted_metric": {
                "init_script" : {
                    "id": "my_init_script"
                },
                "map_script" : {
                    "id": "my_map_script"
                },
                "combine_script" : {
                    "id": "my_combine_script"
                },
                "params": {
                    "field": "amount" 
                },
                "reduce_script" : {
                    "id": "my_reduce_script"
                }
            }
        }
    }
}
  1. 必须在全局 params 对象中指定 initmap 和合并脚本的脚本参数,以便可以在脚本之间共享。

有关指定脚本的更多详细信息,请参见脚本文档.

允许的返回类型

虽然任何有效的脚本对象都可以在一个脚本中使用,但脚本必须返回或存储在 state 对象中只有以下类型:

  • 基本类型
  • 字符串
  • 映射 (只包含此处列出的类型的键和值)
  • 数组 (仅包含此处列出的类型的元素)

脚本范围

脚本度量聚合在执行的 4 个阶段使用脚本:

  • init_script
    在收集任何文件之前执行。允许聚合设置任何初始状态。
    在上面的示例中,init_script 在 state 对象中创建一个数组 transactions
  • map_script

每个收集的文档执行一次。这是必需的脚本。如果未指定 combine_script,则需要将结果状态存储在 state 对象中。

在上面的示例中,map_script 检查类型字段的值。如果值为 sale,则金额字段的值将添加到交易数组中。如果类型字段的值不是 sale,则金额字段的取反值将添加到交易中。

  • combine_script

    文档收集完成后,对每个分片执行一次。这是必需的脚本。允许聚合合并从每个分片返回的状态。

    在上面的示例中,combine_script 遍历所有存储的交易,对 profit 变量中的值求和,最后返回 profit

  • reduce_script

    所有分片均返回其结果后,在协调节点上执行一次。这是必需的脚本。该脚本可以访问变量 states,该变量是每个分片上 combine_script 结果的数组。

    在上面的示例中,reduce_script 通过每个分片返回的 profit 进行迭代,然后在返回最终的合并利润之前返回每个合并的值,该合并后的利润将被返回。

工作示例

想象一下您将以下文档编入带有 2 个分片的索引的情况

PUT /transactions/_bulk?refresh
{"index":{"_id":1}}
{"type": "sale","amount": 80}
{"index":{"_id":2}}
{"type": "cost","amount": 10}
{"index":{"_id":3}}
{"type": "cost","amount": 30}
{"index":{"_id":4}}
{"type": "sale","amount": 130}

可以说文档 1 和 3 最终位于分片 A 上,而文档 2 和 4 最终位于分片 B 上。以下是上述示例各阶段汇总结果的细目分类。

初始化 init_script

state 被初始化为新的空对象。

"state"  :  {}

初始化 init_script 后

在执行任何文档收集之前,此操作在每个分片上运行一次,因此我们在每个分片上都有一个副本:
分片 A

"state"  :  {  "transactions"  :  []  }

分片 B

"state"  :  {  "transactions"  :  []  }

map_script 后

每个分片都会收集其文档,并在收集到的每个文档上运行 map_script:
分片 A

"state"  :  {  "transactions"  :  [  80,  -30  ]  }

分片 B

"state"  :  {  "transactions"  :  [  -10,  130  ]  }

combine_script 之后

在文档收集完成之后,对每个分片执行 combine_script,并将每个分片的所有交易降低为单个利润数字 (通过对交易数组中的值求和),然后将其传递回协调节点:

分片 A

50

分片 B

120

reduce_script 之后

reduce_script 接受另一个 states 数组,其中包含每个分片的合并脚本的结果:

"states" : [
    50,
    120
]

它将分片的响应降低到最终的总利润数字 (通过对值求和),并将其作为聚合结果返回以产生响应:

{
    ...

    "aggregations": {
        "profit": {
            "value": 170
        }
   }
}

其他参数

  • params
    可选的。一个对象,其内容将作为变量传递到 init_scriptmap_script 和 combine_script。这对于允许用户控制聚合的行为以及在脚本之间存储状态很有用。如果未指定,则默认等效于提供:

      "params"  :  {} 
    

空桶

如果脚本化指标聚合的父桶未收集任何文档,则分片将返回空聚合响应,并带有 null 值。在这种情况下,reduce_script 的 status 变量将包含 null 作为该分片的响应。因此,reduce_script 应该期望并处理分片的 null 响应。

3.1.12 统计聚合

多值指标聚合,可根据从聚合文档中提取的数值计算统计信息。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。

返回的统计信息包括:minmaxsumcount 和 avg

假设数据由代表学生考试成绩 (0 至 100) 的文档组成

POST /exams/_search?size=0
{
    "aggs" : {
        "grades_stats" : { "stats" : { "field" : "grade" } }
    }
}

上面的汇总计算了所有文档的成绩统计。聚合类型为统计信息,并且字段设置定义了计算统计信息所依据的文档的数字字段。上面将返回以下内容:

{
    ...

    "aggregations": {
        "grades_stats": {
            "count": 2,
            "min": 50.0,
            "max": 100.0,
            "avg": 75.0,
            "sum": 150.0
        }
    }
}

聚合的名称 (上面的 grades_stats ) 还用作可以从返回的响应中检索聚合结果的键。

脚本

根据脚本计算成绩统计信息:

POST /exams/_search?size=0
{
    "aggs" : {
        "grades_stats" : {
             "stats" : {
                 "script" : {
                     "lang": "painless",
                     "source": "doc['grade'].value"
                 }
             }
         }
    }
}

这会将脚本参数解释为具有轻松且没有脚本参数的嵌入式脚本语言。要使用存储的脚本,请使用以下语法:

POST /exams/_search?size=0
{
    "aggs" : {
        "grades_stats" : {
            "stats" : {
                "script" : {
                    "id": "my_script",
                    "params" : {
                        "field" : "grade"
                    }
                }
            }
        }
    }
}
值脚本

事实证明,该考试远远超出了学生的水平,因此需要进行成绩更正。我们可以使用值脚本来获取新的统计信息:

POST /exams/_search?size=0
{
    "aggs" : {
        "grades_stats" : {
            "stats" : {
                "field" : "grade",
                "script" : {
                    "lang": "painless",
                    "source": "_value * params.correction",
                    "params" : {
                        "correction" : 1.2
                    }
                }
            }
        }
    }
}

缺失值

missing 参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值

POST /exams/_search?size=0
{
    "aggs" : {
        "grades_stats" : {
            "stats" : {
                "field" : "grade",
                "missing": 0 
            }
        }
    }
}

3.1.13 总数聚合

单值指标聚合,汇总从聚合文档中提取的数值。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。

假设数据由代表销售记录的文档组成,我们可以对所有帽子的销售价格求和:

POST /sales/_search?size=0
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "match" : { "type" : "hat" }
            }
        }
    },
    "aggs" : {
        "hat_prices" : { "sum" : { "field" : "price" } }
    }
}

结果:

{
    ...
    "aggregations": {
        "hat_prices": {
           "value": 450.0
        }
    }
}

聚合的名称 (上面的 hat_prices) 还用作可以从返回的响应中检索聚合结果的键。

脚本

我们还可以使用脚本来获取销售价格

POST /sales/_search?size=0
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "match" : { "type" : "hat" }
            }
        }
    },
    "aggs" : {
        "hat_prices" : {
            "sum" : {
                "script" : {
                   "source": "doc.price.value"
                }
            }
        }
    }
}

这会将 script 参数解释为具有 painless 脚本语言且没有脚本参数的 inline 脚本。要使用存储的脚本,请使用以下语法:

  POST /sales/_search?size=0
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "match" : { "type" : "hat" }
            }
        }
    },
    "aggs" : {
        "hat_prices" : {
            "sum" : {
                "script" : {
                    "id": "my_script",
                    "params" : {
                        "field" : "price"
                    }
                }
            }
        }
    }
}
值脚本

也可以使用_value 从脚本访问字段值。例如,这将求和所有帽子的价格的平方:

POST /sales/_search?size=0
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "match" : { "type" : "hat" }
            }
        }
    },
    "aggs" : {
        "square_hats" : {
            "sum" : {
                "field" : "price",
                "script" : {
                    "source": "_value * _value"
                }
            }
        }
    }
}

缺失值

missing 参数定义应如何处理缺少值的文档。默认情况下,缺少该值的文档将被忽略,但也可以将它们视为具有值的文档。例如,这将所有不带价格的帽子销售视为 100

POST /sales/_search?size=0
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "match" : { "type" : "hat" }
            }
        }
    },
    "aggs" : {
        "hat_prices" : {
            "sum" : {
                "field" : "price",
                "missing": 100 
            }
        }
    }
}

3.1.14 热门聚合

最热点聚合

top_hits 度量聚合器跟踪要聚合的最相关文档。该聚合器旨在用作子聚合器,以便可以按存储分区汇总最匹配的文档。

top_hits 聚合器可以有效地用于通过存储桶聚合器按某些字段对结果集进行分组。一个或多个存储桶聚合器确定将结果集切成哪些属性。

选项

  • from - 与要获取的第一个结果的偏移量。
  • size - 每个存储区要返回的最匹配匹配项的最大数目。默认情况下,返回前三个匹配项。
  • sort - 应该如何对最匹配的匹配进行排序。默认情况下,命中按主要查询的分数排序。

支持每次点击功能

top_hits 聚合返回常规搜索命中,因为可以支持许多每次命中功能:

示例

在以下示例中,我们按类型对销售进行分组,并且按类型将最后的销售显示。对于每个销售,源中仅包含日期和价格字段。

POST /sales/_search?size=0
{
    "aggs": {
        "top_tags": {
            "terms": {
                "field": "type",
                "size": 3
            },
            "aggs": {
                "top_sales_hits": {
                    "top_hits": {
                        "sort": [
                            {
                                "date": {
                                    "order": "desc"
                                }
                            }
                        ],
                        "_source": {
                            "includes": [ "date", "price" ]
                        },
                        "size" : 1
                    }
                }
            }
        }
    }
}

可能的响应:

{
  ...
  "aggregations": {
    "top_tags": {
       "doc_count_error_upper_bound": 0,
       "sum_other_doc_count": 0,
       "buckets": [
          {
             "key": "hat",
             "doc_count": 3,
             "top_sales_hits": {
                "hits": {
                   "total" : {
                       "value": 3,
                       "relation": "eq"
                   },
                   "max_score": null,
                   "hits": [
                      {
                         "_index": "sales",
                         "_type": "_doc",
                         "_id": "AVnNBmauCQpcRyxw6ChK",
                         "_source": {
                            "date": "2015/03/01 00:00:00",
                            "price": 200
                         },
                         "sort": [
                            1425168000000
                         ],
                         "_score": null
                      }
                   ]
                }
             }
          },
          {
             "key": "t-shirt",
             "doc_count": 3,
             "top_sales_hits": {
                "hits": {
                   "total" : {
                       "value": 3,
                       "relation": "eq"
                   },
                   "max_score": null,
                   "hits": [
                      {
                         "_index": "sales",
                         "_type": "_doc",
                         "_id": "AVnNBmauCQpcRyxw6ChL",
                         "_source": {
                            "date": "2015/03/01 00:00:00",
                            "price": 175
                         },
                         "sort": [
                            1425168000000
                         ],
                         "_score": null
                      }
                   ]
                }
             }
          },
          {
             "key": "bag",
             "doc_count": 1,
             "top_sales_hits": {
                "hits": {
                   "total" : {
                       "value": 1,
                       "relation": "eq"
                   },
                   "max_score": null,
                   "hits": [
                      {
                         "_index": "sales",
                         "_type": "_doc",
                         "_id": "AVnNBmatCQpcRyxw6ChH",
                         "_source": {
                            "date": "2015/01/01 00:00:00",
                            "price": 150
                         },
                         "sort": [
                            1420070400000
                         ],
                         "_score": null
                      }
                   ]
                }
             }
          }
       ]
    }
  }
}

字段折叠示例

字段折叠或结果分组是一项功能,可将结果集按逻辑分组,然后按组返回顶级文档。组的顺序由组中第一个文档的相关性确定。在 Elasticsearch 中,这可以通过将 top_hits 聚合器包装为子聚合器的存储桶聚合器来实现。

在下面的示例中,我们搜索了所有已爬的网页。对于每个网页,我们存储该网页所属的正文和域名。通过在 domain 字段中定义 terms 聚合器,我们可以按域对网页结果集进行分组。然后将 top_hits 聚合器定义为子聚合器,以便按存储桶收集最匹配的匹配项。

还定义了 max 聚合器,terms 聚合器的排序功能使用该聚合器根据存储桶中最相关文档的相关性顺序返回存储桶。

POST /sales/_search
{
  "query": {
    "match": {
      "body": "elections"
    }
  },
  "aggs": {
    "top_sites": {
      "terms": {
        "field": "domain",
        "order": {
          "top_hit": "desc"
        }
      },
      "aggs": {
        "top_tags_hits": {
          "top_hits": {}
        },
        "top_hit" : {
          "max": {
            "script": {
              "source": "_score"
            }
          }
        }
      }
    }
  }
} 

目前需要最大 (或最小) 聚合器,以确保根据每个域中最相关网页的分数对术语聚合器中的存储桶进行排序。遗憾的是,top_hits 聚合器尚不能在术语聚合器的 order 选项中使用。

top_hits 支持嵌套或反向嵌套聚合器

如果 top_hits 聚合包在 nested 或 reverse_nested 聚合中,则返回嵌套的匹配数据。从某种意义上说,嵌套命中是隐藏的迷你文档,它们是常规文档的一部分,在映射中已配置了嵌套字段类型。如果 top_hits 聚合包装在 nested 或 reverse_nested 聚合中,则可以取消隐藏这些文档。在嵌套类型映射中了解有关嵌套的更多信息。

如果已配置嵌套类型,则单个文档实际上将被索引为多个 Lucene 文档,并且它们共享相同的 ID。为了确定嵌套匹配的身份,除了 ID 之外,还需要更多的信息,因此这就是嵌套匹配还包括其嵌套身份的原因。嵌套标识保留在搜索匹配的_nested 字段下,并包括数组字段和嵌套匹配所属的数组字段中的偏移量。偏移量基于零。

让我们看看它如何与真实例子一起工作。考虑以下映射:

 PUT /sales
{
    "mappings": {
        "properties" : {
            "tags" : { "type" : "keyword" },
            "comments" : { 
                "type" : "nested",
                "properties" : {
                    "username" : { "type" : "keyword" },
                    "comment" : { "type" : "text" }
                }
            }
        }
    }
} 
  1. 注释是一个数组,用于在产品对象下保存嵌套文档 .

一些文档

PUT /sales/_doc/1?refresh
{
    "tags": ["car", "auto"],
    "comments": [
        {"username": "baddriver007", "comment": "This car could have better brakes"},
        {"username": "dr_who", "comment": "Where's the autopilot? Can't find it"},
        {"username": "ilovemotorbikes", "comment": "This car has two extra wheels"}
    ]
}

现在可以执行以下顶级点击聚合 (包装在嵌套聚合中):

 POST /sales/_search
{
    "query": {
        "term": { "tags": "car" }
    },
    "aggs": {
        "by_sale": {
            "nested" : {
                "path" : "comments"
            },
            "aggs": {
                "by_user": {
                    "terms": {
                        "field": "comments.username",
                        "size": 1
                    },
                    "aggs": {
                        "by_nested": {
                            "top_hits":{}
                        }
                    }
                }
            }
        }
    }
}

具有嵌套匹配项的热门匹配项摘要,位于数组字段 comments 的第一个插槽中:

{
  ...
  "aggregations": {
    "by_sale": {
      "by_user": {
        "buckets": [
          {
            "key": "baddriver007",
            "doc_count": 1,
            "by_nested": {
              "hits": {
                "total" : {
                   "value": 1,
                   "relation": "eq"
                },
                "max_score": 0.3616575,
                "hits": [
                  {
                    "_index": "sales",
                    "_type" : "_doc",
                    "_id": "1",
                    "_nested": {
                      "field": "comments",  
                      "offset": 0 
                    },
                    "_score": 0.3616575,
                    "_source": {
                      "comment": "This car could have better brakes", 
                      "username": "baddriver007"
                    }
                  }
                ]
              }
            }
          }
          ...
        ]
      }
    }
  }
}
  1. 包含嵌套匹配项的数组字段的名称
  2. 如果嵌套命中在包含数组中
  3. 嵌套匹配的来源

如果请求_source,则返回嵌套对象源的一部分,而不是文档的整个源。还可以通过位于 nested 或 reverse_nested 聚合中的 top_hits 聚合访问嵌套对象内部级别上的存储字段。

仅嵌套匹配在匹配中具有_nested 字段,非嵌套 (常规) 匹配将没有_nested 字段。

如果未启用_source,则_nested 中的信息还可用于解析其他地方的原始源。

在下面的示例中,嵌套匹配项位于 nested_grand_child_field 字段的第一个插槽中,然后位于 nested_child_field 字段的第二个慢速位置中:

...
"hits": {
 "total" : {
     "value": 2565,
     "relation": "eq"
 },
 "max_score": 1,
 "hits": [
   {
     "_index": "a",
     "_type": "b",
     "_id": "1",
     "_score": 1,
     "_nested" : {
       "field" : "nested_child_field",
       "offset" : 1,
       "_nested" : {
         "field" : "nested_grand_child_field",
         "offset" : 0
       }
     }
     "_source": ...
   },
   ...
 ]
}
...

3.1.15 值计数聚合

单值度量标准聚合,用于计算从聚合文档中提取的值的数量。这些值可以从文档中的特定字段中提取,也可以由提供的脚本生成。通常,此聚合器将与其他单值聚合一起使用。例如,在计算 avg 时,可能会对计算平均值的值数量感兴趣。

POST /sales/_search?size=0
{
    "aggs" : {
        "types_count" : { "value_count" : { "field" : "type" } }
    }
}

响应结果:

{
    ...
    "aggregations": {
        "types_count": {
            "value": 7
        }
    }
}

聚合的名称 (上面的 types_count) 也是可以从返回的响应中检索聚合结果的键。

脚本

计算脚本生成的值:

POST /sales/_search?size=0
{
    "aggs" : {
        "type_count" : {
            "value_count" : {
                "script" : {
                    "source" : "doc['type'].value"
                }
            }
        }
    }
}

这会将脚本参数解释为具有轻松脚本语言且没有脚本参数的嵌入式脚本。要使用存储的脚本,请使用以下语法:

POST /sales/_search?size=0
{
    "aggs" : {
        "types_count" : {
            "value_count" : {
                "script" : {
                    "id": "my_script",
                    "params" : {
                        "field" : "type"
                    }
                }
            }
        }
    }
}

3.1.16 中位数绝对偏差聚合

单值聚合近似于其搜索结果的中位数绝对偏差

中位数绝对偏差是变异性的量度。这是一个可靠的统计信息,这意味着它对于描述可能具有异常值或未正常分布的数据很有用。对于此类数据,它比标准偏差更具描述性。

计算为每个数据点与整个样本的中值的偏差的中值。即,对于随机变量 X,中值绝对偏差为中值 (| median (X)-X〜i〜|)。

示例

假设我们的数据代表一到五星级的产品评论。此类评论通常被概括为平均值,这很容易理解,但却无法描述评论的可变性。估计中值绝对偏差可以洞悉彼此之间有多少差异。

在此示例中,我们的产品平均评分为 3 星。让我们看一下其评分的中位数绝对偏差,以确定它们之间的差异

GET reviews/_search
{
  "size": 0,
  "aggs": {
    "review_average": {
      "avg": {
        "field": "rating"
      }
    },
    "review_variability": {
      "median_absolute_deviation": {
        "field": "rating" 
      }
    }
  }
}
  1. 评分必须是数字字段

所得的 2 的绝对中位数绝对值告诉我们,评级中存在相当大的可变性。评论者必须对此产品有不同的意见。

{
  ...
  "aggregations": {
    "review_average": {
      "value": 3.0
    },
    "review_variability": {
      "value": 2.0
    }
  }
}

近似值

计算中值绝对偏差的幼稚实现将整个样本存储在内存中,因此此聚合计算出一个近似值。 它使用 TDigest 数据结构 来近似估计样本中位数和样本中位数的偏差中位数,有关 TDigests 近似特性的更多信息,请参见 百分位数通常是近似值 

资源使用率与 TDigest 分位数逼近度的精度之间的权衡,以及由此聚合的中位数绝对偏差近似值的精度,由 compression 参数控制。较高的 compression 设置以较高的内存使用量为代价提供了更准确的近似值。有关 TDigestcompression 参数的特征的更多信息,请参见压缩

GET reviews/_search
{
  "size": 0,
  "aggs": {
    "review_variability": {
      "median_absolute_deviation": {
        "field": "rating",
        "compression": 100
      }
    }
  }
}

此聚合的默认压缩值为 1000。在此压缩级别,此聚合通常在精确结果的 5%以内,但是观察到的性能将取决于样本数据。

脚本

此度量标准聚合支持脚本。在上面的示例中,产品评论的规模从 1 到 5。如果我们想将它们修改为 1 到 10 的比例,则可以使用脚本。

要提供内联脚本:

GET reviews/_search
{
  "size": 0,
  "aggs": {
    "review_variability": {
      "median_absolute_deviation": {
        "script": {
          "lang": "painless",
          "source": "doc['rating'].value * params.scaleFactor",
          "params": {
            "scaleFactor": 2
          }
        }
      }
    }
  }
}

提供储存脚本

GET reviews/_search
{
  "size": 0,
  "aggs": {
    "review_variability": {
      "median_absolute_deviation": {
        "script": {
          "id": "my_script",
          "params": {
            "field": "rating"
          }
        }
      }
    }
  }
}

缺失值

missing 参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。

让我们保持乐观,假设有些评论者非常喜欢该产品,以至于他们忘记给它打分了。我们给他们分配五颗星

GET reviews/_search
{
  "size": 0,
  "aggs": {
    "review_variability": {
      "median_absolute_deviation": {
        "field": "rating",
        "missing": 5
      }
    }
  }
}

3.2 桶聚合

桶聚合不像度量聚合那样计算字段的度量,而是创建文档存储桶。每个存储桶都与一个标准 (取决于聚合类型) 相关联,该标准确定当前上下文中的文档是否 “落入” 其中。换句话说,存储桶有效地定义了文档集。除了存储桶本身之外,存储桶聚合还计算并返回 “落入” 每个存储桶的文档数。

桶聚合与 metrics 聚合相反,可以保存子聚合。这些子聚合将针对其” 父”bucket 聚合创建的 bucket 进行聚合。

有不同的存储桶聚合器,每个聚合器都有不同的 “存储桶” 策略。一些定义单个存储桶,一些定义固定数量的多个存储桶,另一些定义在聚合过程中动态创建存储桶。

注意:
单个响应中允许的最大存储桶数受 search.max_buckets 的动态集群设置的限制。它的默认值为 10,000,尝试返回大于限制的请求将失败,并发生异常。

3.2.1邻接矩阵聚合

桶聚合返回一种形式的邻接矩阵。 该请求提供了一个命名过滤器表达式的集合,类似于 filters 聚合请求。响应中的每个存储桶代表相交过滤器矩阵中的一个非空单元。

给定名为 AB 和 C 的过滤器,响应将返回具有以下名称的存储桶:

ABC
AAA&BA&C
BBB&C
CC

相交的存储桶 (例如 A&C) 使用两个与号字符分隔的过滤器名称的组合来标记。请注意,响应中也不包含 “C&A” 存储桶,因为这与 “ A&C” 属于同一组文档。据说矩阵是对称的,所以我们只返回一半。为此,我们对过滤器名称字符串进行排序,并始终使用一对中的最低值作为 “&” 分隔符左侧的值。

如果客户端希望使用除与号默认值以外的分隔符字符串,则可以在请求中传递替代的分隔符参数。
例:

PUT /emails/_bulk?refresh
{ "index" : { "_id" : 1 } }
{ "accounts" : ["hillary", "sidney"]}
{ "index" : { "_id" : 2 } }
{ "accounts" : ["hillary", "donald"]}
{ "index" : { "_id" : 3 } }
{ "accounts" : ["vladimir", "donald"]}

GET emails/_search
{
  "size": 0,
  "aggs" : {
    "interactions" : {
      "adjacency_matrix" : {
        "filters" : {
          "grpA" : { "terms" : { "accounts" : ["hillary", "sidney"] }},
          "grpB" : { "terms" : { "accounts" : ["donald", "mitt"] }},
          "grpC" : { "terms" : { "accounts" : ["vladimir", "nigel"] }}
        }
      }
    }
  }
}

在上面的示例中,我们分析了电子邮件,以查看哪些个人已交换了消息。我们将分别获取每个组的计数,以及已记录交互的成对组的消息计数。

响应:

{
  "took": 9,
  "timed_out": false,
  "_shards": ...,
  "hits": ...,
  "aggregations": {
    "interactions": {
      "buckets": [
        {
          "key":"grpA",
          "doc_count": 2
        },
        {
          "key":"grpA&grpB",
          "doc_count": 1
        },
        {
          "key":"grpB",
          "doc_count": 2
        },
        {
          "key":"grpB&grpC",
          "doc_count": 1
        },
        {
          "key":"grpC",
          "doc_count": 1
        }
      ]
    }
  }
}

用法

这种聚合本身可以提供创建无向加权图所需的所有数据。但是,当与 date_histogram 之类的子聚合一起使用时,结果可以提供执行动态网络分析所需的附加数据级别检查互动随着时间的推移变得重要。

限制

对于 N 个过滤器,所产生的存储桶矩阵可以为 N²/ 2,因此默认情况下最大限制为 100 个过滤器。可以使用 index.max_adjacency_matrix_filters 索引级别设置更改此设置。

3.2.2自动间隔日期直方图聚合

日期聚合直方图除了提供一个间隔来用作每个存储桶的宽度外,还提供了一个目标存储桶数来指示所需的存储桶数,并自动选择存储桶的间隔以最佳地实现该目标。返回的存储桶数将始终小于或等于此目标数。

存储桶字段是可选的,如果未指定,则默认为 10 个存储桶。

要求目标为 10 个存储桶。

POST /sales/_search?size=0
{
    "aggs" : {
        "sales_over_time" : {
            "auto_date_histogram" : {
                "field" : "date",
                "buckets" : 10
            }
        }
    }
}

在内部,日期表示为一个 64 位数字,表示自该时间点以来的毫秒数。这些时间戳作为存储桶键返回。 key_as_string 是使用 format 参数指定的格式转换为格式化日期字符串的相同时间戳:

提示
如果未指定格式,则它将使用在字段映射中指定的第一个日期格式。

 POST /sales/_search?size=0
{
    "aggs" : {
        "sales_over_time" : {
            "auto_date_histogram" : {
                "field" : "date",
                "buckets" : 5,
                "format" : "yyyy-MM-dd" 
            }
        }
    }
}

支持富有表现力的日期格式模式
响应:

 {
    ...
    "aggregations": {
        "sales_over_time": {
            "buckets": [
                {
                    "key_as_string": "2015-01-01",
                    "key": 1420070400000,
                    "doc_count": 3
                },
                {
                    "key_as_string": "2015-02-01",
                    "key": 1422748800000,
                    "doc_count": 2
                },
                {
                    "key_as_string": "2015-03-01",
                    "key": 1425168000000,
                    "doc_count": 2
                }
            ],
            "interval": "1M"
        }
    }
}

时间间隔

根据聚合收集的数据选择返回的存储桶的间隔,以使返回的存储桶数小于或等于请求的数量。返回的可能间隔是:

  • 秒 | 1, 5, 10 和 30 的倍数
  • 分 | 1, 5, 10 和 30 的倍数
  • 时 | 1, 3 和 12 的倍数
  • 日 | 1, 和 7 的倍数
  • 月 | 1, 和 3 的倍数
  • 年 | 1, 5, 10, 20, 50 和 100 的倍数

在最坏的情况下,如果每天的存储桶数超出了请求的存储桶数,则返回的存储桶数将是请求的存储桶数的 1/7。

时区

日期时间存储在 UTC 中的 Elasticsearch 中。默认情况下,所有存储和取整也是在 UTC 中完成的。time_zone 参数可用于指示存储段应使用不同的时区。

时区可以指定为 ISO 8601 UTC 偏移量 (例如 +01:00 或 -08:00),也可以指定为时区 ID ,例如 TZ 数据库中使用的标识符 America / Los_Angeles

考虑以下示例:

PUT my_index/log/1?refresh
{
  "date": "2015-10-01T00:30:00Z"
}

PUT my_index/log/2?refresh
{
  "date": "2015-10-01T01:30:00Z"
}

PUT my_index/log/3?refresh
{
  "date": "2015-10-01T02:30:00Z"
}

GET my_index/_search?size=0
{
  "aggs": {
    "by_day": {
      "auto_date_histogram": {
        "field":     "date",
        "buckets" : 3
      }
    }
  }
}

如果未指定时区,则使用 UTC,从 UTC 2015 年 10 月 1 日午夜开始返回三个 1 小时时段:

{
  ...
  "aggregations": {
    "by_day": {
      "buckets": [
        {
          "key_as_string": "2015-10-01T00:00:00.000Z",
          "key": 1443657600000,
          "doc_count": 1
        },
        {
          "key_as_string": "2015-10-01T01:00:00.000Z",
          "key": 1443661200000,
          "doc_count": 1
        },
        {
          "key_as_string": "2015-10-01T02:00:00.000Z",
          "key": 1443664800000,
          "doc_count": 1
        }
      ],
      "interval": "1h"
    }
  }
}

如果指定了 -01:00 的 time_zone,则午夜在 UTC 午夜前一小时开始:

 GET my_index/_search?size=0
{
  "aggs": {
    "by_day": {
      "auto_date_histogram": {
        "field":     "date",
        "buckets" : 3,
        "time_zone": "-01:00"
      }
    }
  }
}

现在,仍返回三个 1 小时时段,但第一个时段从 2015 年 9 月 30 日晚上 11:00 开始,因为这是该时段在指定时区的本地时间。

{
  ...
  "aggregations": {
    "by_day": {
      "buckets": [
        {
          "key_as_string": "2015-09-30T23:00:00.000-01:00", 
          "key": 1443657600000,
          "doc_count": 1
        },
        {
          "key_as_string": "2015-10-01T00:00:00.000-01:00",
          "key": 1443661200000,
          "doc_count": 1
        },
        {
          "key_as_string": "2015-10-01T01:00:00.000-01:00",
          "key": 1443664800000,
          "doc_count": 1
        }
      ],
      "interval": "1h"
    }
  }
}
  1. key_as_string 值表示指定时区中每一天的午夜。

警告
当使用 DST (夏令时) 更改之后的时区时,接近更改发生时的存储区的大小可能会与相邻存储区的大小略有不同。例如,假设某个夏令时开始于 CET 时区:2016 年 3 月 27 日凌晨 2 点,时钟被调到本地时间 1 小时到凌晨 3 点。如果聚合的结果是每日存储桶,则当天的存储桶将仅保留 23 小时的数据,而不是其他存储桶通常的 24 小时。对于较短的时间间隔 (例如 12 小时在这里,DST 轮换发生的 3 月 27 日上午,我们只有 11 小时的时段。

脚本

像普通的 date_histogram,同时支持文档级脚本和值级脚本。但是,此聚合不支持 min_doc_countextended_bounds 和 order 参数。

最小间隔参数

minimum_interval 允许调用方指定应使用的最小舍入间隔。这可以使收集过程更有效率,因为聚合不会尝试以小于 minimum_interval 的任何时间间隔取整。

minimum_interval 的可接受单位为:

  • POST /sales/_search?size=0
    {
    "aggs" : {
        "sale_date" : {
             "auto_date_histogram" : {
                 "field" : "date",
                 "buckets": 10,
                 "minimum_interval": "minute"
             }
         }
    }
    }
    

缺失值

missing 参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。

POST /sales/_search?size=0
{
    "aggs" : {
        "sale_date" : {
             "auto_date_histogram" : {
                 "field" : "date",
                 "buckets": 10,
                 "missing": "2000/01/01" 
             }
         }
    }
}

3.2.3 子聚合

一种特殊的单存储桶聚合,用于选择具有指定类型的子文档,如 join 字段

该聚合只有一个选项:

  • type- 应选择的子类型。

例如,假设我们有一个问题和答案的索引。答案类型在映射中具有以下 join 字段:

PUT child_example
{
  "mappings": {
    "properties": {
      "join": {
        "type": "join",
        "relations": {
          "question": "answer"
        }
      }
    }
  }
}

question 文档包含一个标记字段,而 answer 文档包含一个所有者字段。使用子级聚合,即使两个字段存在两种不同的文档中,也可以在单个请求中将标签存储桶映射到所有者存储桶。

问题文档的示例:

PUT child_example/_doc/1
{
  "join": {
    "name": "question"
  },
  "body": "<p>I have Windows 2003 server and i bought a new Windows 2008 server...",
  "title": "Whats the best way to file transfer my site from server to a newer one?",
  "tags": [
    "windows-server-2003",
    "windows-server-2008",
    "file-transfer"
  ]
}

answer 文档的示例:

PUT child_example/_doc/2?routing=1
{
  "join": {
    "name": "answer",
    "parent": "1"
  },
  "owner": {
    "location": "Norfolk, United Kingdom",
    "display_name": "Sam",
    "id": 48
  },
  "body": "<p>Unfortunately you're pretty much limited to FTP...",
  "creation_date": "2009-05-04T13:45:37.030"
}

PUT child_example/_doc/3?routing=1&refresh
{
  "join": {
    "name": "answer",
    "parent": "1"
  },
  "owner": {
    "location": "Norfolk, United Kingdom",
    "display_name": "Troll",
    "id": 49
  },
  "body": "<p>Use Linux...",
  "creation_date": "2009-05-05T13:45:37.030"
}

可以建立以下将两者连接在一起的请求:

POST child_example/_search?size=0
{
  "aggs": {
    "top-tags": {
      "terms": {
        "field": "tags.keyword",
        "size": 10
      },
      "aggs": {
        "to-answers": {
          "children": {
            "type" : "answer" 
          },
          "aggs": {
            "top-names": {
              "terms": {
                "field": "owner.display_name.keyword",
                "size": 10
              }
            }
          }
        }
      }
    }
  }
}
  1. type 指向名称为 answer 的类型 / 映射。

上面的示例返回顶部的问题标签,每个标签返回顶部的答案所有者。

可能的响应:

{
  "took": 25,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped" : 0,
    "failed": 0
  },
  "hits": {
    "total" : {
      "value": 3,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  },
  "aggregations": {
    "top-tags": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "file-transfer",
          "doc_count": 1, 
          "to-answers": {
            "doc_count": 2, 
            "top-names": {
              "doc_count_error_upper_bound": 0,
              "sum_other_doc_count": 0,
              "buckets": [
                {
                  "key": "Sam",
                  "doc_count": 1
                },
                {
                  "key": "Troll",
                  "doc_count": 1
                }
              ]
            }
          }
        },
        {
          "key": "windows-server-2003",
          "doc_count": 1, 
          "to-answers": {
            "doc_count": 2, 
            "top-names": {
              "doc_count_error_upper_bound": 0,
              "sum_other_doc_count": 0,
              "buckets": [
                {
                  "key": "Sam",
                  "doc_count": 1
                },
                {
                  "key": "Troll",
                  "doc_count": 1
                }
              ]
            }
          }
        },
        {
          "key": "windows-server-2008",
          "doc_count": 1, 
          "to-answers": {
            "doc_count": 2, 
            "top-names": {
              "doc_count_error_upper_bound": 0,
              "sum_other_doc_count": 0,
              "buckets": [
                {
                  "key": "Sam",
                  "doc_count": 1
                },
                {
                  "key": "Troll",
                  "doc_count": 1
                }
              ]
            }
          }
        }
      ]
    }
  }
}
  1. 标签为 file-transferwindows-server-2003 等的问题文档的数量。

  2. 与标记为 file-transferwindows-server-2003 等的问题文档相关的回答文档的数量。

3.2.4复合聚合

多桶聚合是指从不同的源创建的复合桶.

与其他 多桶 聚合不同的是,复合 聚合可用于高效地对多层聚合中的 所有 桶进行分页。 这种聚合提供了一种流特定聚合的所有桶的方法,类似于 scroll 对文档做的操作。

复合桶是根据每个文档提取 / 创建的值的组合构建的,每个组合都被视为一个复合桶。

例如下面的文档:

{
    "keyword": ["foo", "bar"],
    "number": [23, 65, 76]
}

… 创建下面的复合桶, 使用 keyword 和 number 两个字段的值进行聚合:

{ "keyword": "foo", "number": 23 }
{ "keyword": "foo", "number": 65 }
{ "keyword": "foo", "number": 76 }
{ "keyword": "bar", "number": 23 }
{ "keyword": "bar", "number": 65 }
{ "keyword": "bar", "number": 76 }

Values source

sources 参数控制用于构建复合桶的源。 定义 sources 的顺序很重要,因为它还控制着键的返回顺序。

每个 sources 的名称必须是唯一的.

下面是三种不同类型的 values source:

Terms

terms 的 value source 相当于一个简单的 terms 聚合。 这些值是从 field 或 script 中提取的,与 terms 聚合完全一样。

使用 field 为复合桶创建值:

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "sources" : [
                    { "product": { "terms" : { "field": "product" } } }
                ]
            }
        }
     }
}

也可以使用 script 为复合桶创建值:

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "sources" : [
                    {
                        "product": {
                            "terms" : {
                                "script" : {
                                    "source": "doc['product'].value",
                                    "lang": "painless"
                                }
                            }
                        }
                    }
                ]
            }
        }
    }
}
Histogram

histogram 值源可应用于数值,以在值上构建固定大小的间隔。 interval 参数定义如何转换数值。 例如,将 interval 的值设为 5,会将任何数值转换到最接近的间隔, 值 101 将会被转换为 100 ,这是这个键的间隔在 100 到 105 之间。

Example:

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "sources" : [
                    { "histo": { "histogram" : { "field": "price", "interval": 5 } } }
                ]
            }
        }
    }
}

这些值是从数字字段或返回数字值的脚本中构建的:

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "sources" : [
                    {
                        "histo": {
                            "histogram" : {
                                "interval": 5,
                                "script" : {
                                    "source": "doc['price'].value",
                                    "lang": "painless"
                                }
                            }
                        }
                    }
                ]
            }
        }
    }
}
Date Histogram

date_histogram 与 histogram 值源相似,但不同之处是由日期 / 时间表达式指定的时间间隔::

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "sources" : [
                    { "date": { "date_histogram" : { "field": "timestamp", "calendar_interval": "1d" } } }
                ]
            }
        }
    }
}

上面的示例每天创建一个间隔,并将所有 timestamp 值转换为其最接近的间隔的开始值。间隔的可用表达式:季度hour分钟

时间值也可以通过时间单位支持的缩写来指定。 请注意,不支持小数时间值,但是您可以通过转移到其他时间单位来解决此问题 (例如,可以将 1.5h 指定为 90 分钟)。

Format

在内部,日期表示为一个 64 位的数字,表示自该时间点以来的毫秒数。这些时间戳作为存储桶键返回。可以使用格式化参数指定的格式来返回格式化的日期字符串:

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "sources" : [
                    {
                        "date": {
                            "date_histogram" : {
                                "field": "timestamp",
                                "calendar_interval": "1d",
                                "format": "yyyy-MM-dd" 
                            }
                        }
                    }
                ]
            }
        }
    }
}
  1. 支持表示日期格式模式

时区

Elasticsearch 使用世界标准时间(UTC)存储日期时间。 默认情况下,所有存储和取整也是使用世界标准时间(UTC)。 time_zone 参数用于指定存储时所使用不同的时区

时区可以指定为 ISO 8601 UTC 偏移 (例如 +01:00 或者 -08:00) , 也可以指定为时区数据库(TimeZoneDB)中的时区编号,例如 America/Los_Angeles.

混合不同的值源

sources 参数数接受源数组的值。可以混合使用不同的值源来创建复合存储桶。例如:

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "sources" : [
                    { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } },
                    { "product": { "terms": {"field": "product" } } }
                ]
            }
        }
    }
}

这将从两个值源创建的值中( date_histogram 和 terms)创建复合桶。
每个桶均由两个值组成,其中每一个值源都在聚合中定义。 允许任何类型的组合,并且在存储桶中,数组中的排序将被保留。

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "sources" : [
                    { "shop": { "terms": {"field": "shop" } } },
                    { "product": { "terms": { "field": "product" } } },
                    { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } }
                ]
            }
        }
    }
}

排序

默认情况下,复合桶按其自然顺序排序。 值按其值的升序排序。 当请求多个值源时,将对每个值源进行排序, 将复合桶的第一个值与另一个复合桶的第一个值进行比较 ,如果它们相等,将比较复合桶中的下一个值。 这就意味着复合桶 [foo, 100] 被认为小于 [foobar, 0] ,因为 foo 被认为小于 foobar。 可以通过直接将 order 设置为 asc (默认的) 或者 desc (降序) 来定义每个值源的排序方式。例如:

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "sources" : [
                    { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } },
                    { "product": { "terms": {"field": "product", "order": "asc" } } }
                ]
            }
        }
    }
}

… 当比较源为 date_histogram 的值时,将按降序对复合桶进行排序;当比较源 terms 的值时,将按升序对复合桶进行排序。

缺失桶

默认情况下,没有给定源值的文档将被忽略。 可以通过将 missing_bucket 设置为 true (默认值是 false),把它们包括在响应中。例如 :

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "sources" : [
                    { "product_name": { "terms" : { "field": "product", "missing_bucket": true } } }
                ]
            }
        }
     }
}

在上面的示例中,当 product 字段没有值时,源 product_name 将会被设置一个显式的 null 值 。 通过源 order 参数指定 null 值应该排在首位 (升序,asc) 或者是末尾 (降序,desc).

设置返回数量

通过设置 size 参数指定返回复合桶的数量。 每个复合桶都被视为单个桶, 当我们将 size 指定为 10 时,将返回从源值创建的前 10 个复合桶。 响应包含数组中每个复合桶的值,该数组包含从每个值源提取的值。

分页

如果复合桶的数量过多 (或者不知其数量) ,从而无法在单个响应中返回,那么可以将检索拆分为多个请求。 由于复合存储桶在本质上是扁平的,所以请求的 size 恰好是将在响应中返回的复合存储桶的数量 (假设它们能够返回 size 的复合桶). 如果要检索所有复合存储桶,最好使用较小的 size (100 或者 1000) ,然后使用 after 参数去检索余下的结果。 例如:

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "size": 2,
                "sources" : [
                    { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } },
                    { "product": { "terms": {"field": "product" } } }
                ]
            }
        }
    }
}

… 返回的结果:

{
    ...
    "aggregations": {
        "my_buckets": {
            "after_key": {"date": 1494288000000,
                "product": "mad max"
            },
            "buckets": [
                {
                    "key": {
                        "date": 1494201600000,
                        "product": "rocky"
                    },
                    "doc_count": 1
                },
                {
                    "key": {
                        "date": 1494288000000,
                        "product": "mad max"
                    },
                    "doc_count": 2
                }
            ]
        }
    }
}

①. 查询最后一个复合桶。

注意
after_key 等于 Pipeline aggregations. 执行任何过滤前返回的最后一个桶 。如果通过 pipeline aggregation(管道聚合)过滤 / 删除全部的桶,那么 after_key 将包含过滤前的最后一个桶.

after 参数可以被用于检索在前一个检索中返回的最后一个复合桶。 下面的示例中, 可以在 after_key 值中找到前一个检索中返回的最后一个桶,并且根据该值,使用下面的方法进行分页检索:

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                "size": 2,
                 "sources" : [
                    { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } },
                    { "product": { "terms": {"field": "product", "order": "asc" } } }
                ],
                "after": { "date": 1494288000000, "product": "mad max" } ①
            }
        }
    }
}

① 聚合中的排序字段应该使用 after 的字段.

子聚合

像 multi-bucket (多桶)聚合一样, composite (复合)聚合也可以包含子聚合。这些子聚合可用于计算其他存储桶或有关此父聚合创建的每个复合桶的统计信息。 如,以下示例计算每个复合桶字段的平均值:

GET /_search
{
    "aggs" : {
        "my_buckets": {
            "composite" : {
                 "sources" : [
                    { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } },
                    { "product": { "terms": {"field": "product" } } }
                ]
            },
            "aggregations": {
                "the_avg": {
                    "avg": { "field": "price" }
                }
            }
        }
    }
}

… returns:

{
    ...
    "aggregations": {
        "my_buckets": {
            "after_key": {
                "date": 1494201600000,
                "product": "rocky"
            },
            "buckets": [
                {
                    "key": {
                        "date": 1494460800000,
                        "product": "apocalypse now"
                    },
                    "doc_count": 1,
                    "the_avg": {
                        "value": 10.0
                    }
                },
                {
                    "key": {
                        "date": 1494374400000,
                        "product": "mad max"
                    },
                    "doc_count": 1,
                    "the_avg": {
                        "value": 27.0
                    }
                },
                {
                    "key": {
                        "date": 1494288000000,
                        "product" : "mad max"
                    },
                    "doc_count": 2,
                    "the_avg": {
                        "value": 22.5
                    }
                },
                {
                    "key": {
                        "date": 1494201600000,
                        "product": "rocky"
                    },
                    "doc_count": 1,
                    "the_avg": {
                        "value": 10.0
                    }
                }
            ]
        }
    }
}

Pipeline aggregations(管道聚合)

当前复合聚合与管道聚合并不兼容,在大多数情况下也没有意义。 例如, 由于复合聚合的分页特性,单个逻辑分区 (例如一天) 可能分布在多个页面上。 由于管道聚合纯粹是对桶的最终列表的后处理,因此在复合页面上运行类似派生的操作可能会导致不准确的结果,因为它只考虑了该页面上的 “部分” 结果。

将来可能会支持自我包含在单个桶中的管道聚合 (例如 bucket_selector)

3.2.5日期直方图聚合

这种多桶聚合类似于普通的 histogram, 但只能与日期值一起使用。由于日期在 Elasticsearch 中内部以长值表示,因此在日期中使用普通的 histogram 可能并不准确。 两种 API 的主要区别在于,可以使用日期 / 时间表达式指定间隔。基于时间的数据需要特殊的支持,因为基于时间的间隔并不总是固定长度。

日历和固定间隔

当我们配置日期直方图聚合时,可以通过两种方式指定时间间隔:日历感知时间间隔和固定时间间隔。

日历感知间隔在夏令时会更改特定日期的时间长度,月份具有不同的天数,并且可以将闰秒附加到特定年份。

相比之下,固定间隔始终是 SI 单位的倍数,并且不会根据日历环境而变化。

注意
组合 interval 字段已被弃用
在 [7.2] 中 interval 字段已被弃用
历史上,日历和固定间隔都是在单个 interval 字段中配置的,这导致语义混乱。指定 1d 将假定为日历感知时间,而 2d 将其解释为固定时间。 为了获得固定时间的 “一天” ,用户需要指定下一个较小的单位 (在这种情况下为,24h).
这种组合行为通常对于用户来说是未知的,即使知道该行为,也很难使用和混淆。
不建议使用此行为,而推荐使用两个新的显式字段: calendar_interval 和 fixed_interval.
通过在日历和时间间隔之间强制进行选择,时间间隔的语义对用户而言立即清晰可见,并且没有歧义。 旧的 interval 字段将在以后的版本中删除。

日历间隔

日历感知间隔使用 calendar_interval 参数进行配置。 日历间隔只能以 “单数” 为单位指定 (1d1M, 等等)。 不支持倍数,如 2d, 将引发异常。

日历感知间隔可接受的单位为:

  • 分 (m1m)

所有分钟数从 00 秒开始。

一分钟是指定时区中第一分钟的 00 秒与后一分钟的 00 秒之间的时间间隔,补偿任何中间的闰秒,因此,每小时的分钟数和秒数在开始和结束时相同。

  • 小时 (h1h)

所有小时都始于 00 分 00 秒。

一小时 (1h) 是在指定时区的第一个小时的 00:00 分钟与第二个小时的下一个小时 00:00 分钟之间的时间间隔,可补偿任何中间的润秒。因此,每小时的分钟数和秒数在开始和结束时相同。

  • 天 (d1d)

所有天都是从最早的时间开始,通常是 00:00:00 (凌晨).

一天 (1d) 是指定时区中一天的开始与第二天的开始之间的间隔,补偿任何中间的时间变化。

  • 周 (w1w)

一周是从指定的时区以一周的同一天为开始到下一周的时间之间的间隔。

  • 月 (M1M)

一个月是指在指定时区中,月份的开始日期和时间与月份的同一天和时间之间的间隔,月份中日期和时间在开始和结束时相同。

  • 季度 (q1q)

一个季度 (1q) 是指月份开始日和当天的时间到三个月后同一天相同时间的时间间隔。

  • 年 (y1y)

一年 (1y) 是指在指定时区中, 从开始月份的日期和时间与下一年同一天和相同时间之间的间隔,日期和时间在开始和结束时相同。

日历间隔示例

例如,下面是一个以月为时间间隔的聚合:

POST /sales/_search?size=0
{
    "aggs" : {
        "sales_over_time" : {
            "date_histogram" : {
                "field" : "date",
                "calendar_interval" : "month"
            }
        }
    }
}

如果尝试使用倍数日历单位,则聚合将失败,因为日历间隔仅支持单个日历单位:

POST /sales/_search?size=0
{
    "aggs" : {
        "sales_over_time" : {
            "date_histogram" : {
                "field" : "date",
                "calendar_interval" : "2d"
            }
        }
    }
}
{
  "error" : {
    "root_cause" : [...],
    "type" : "x_content_parse_exception",
    "reason" : "[1:82] [date_histogram] failed to parse field [calendar_interval]",
    "caused_by" : {
      "type" : "illegal_argument_exception",
      "reason" : "The supplied interval [2d] could not be parsed as a calendar interval.",
      "stack_trace" : "java.lang.IllegalArgumentException: The supplied interval [2d] could not be parsed as a calendar interval."
    }
  }
}

日期直方图聚合

固定间隔

固定间隔使用 fixed_interval 参数进行配置。

与日历感知间隔相比,固定间隔是固定数量的 SI 单位,无论它们位于日历上的什么位置,它们都不会偏离。就像一秒始终由 1000 毫秒组成。这样在任何多个受支持单位上都可以指定固定间隔。

然而,这也意味着固定间隔不能表示像月份这样的单位,因为一个月的时间并不是一个确切的值。尝试指定一个像月份或者季度这样的日历间隔会引发异常。

固定间隔可接受的单位有:

  • 毫秒 (ms)

  • 秒 (s)

    • 定义为 1000 毫秒每秒
  • 分钟 (m)

    • 每分钟都从 00 秒开始。
      定义为 60 秒每分钟 (60,000 毫秒)
  • 小时 (h)

    • 每小时都从 00 分 00 秒开始。定义为 60 分钟每小时 (3,600,000 毫秒)
  • 天 (d)

    • 每天都从最早的 00:00:00 (午夜) 开始。

定义为 24 小时每天 (86,400,000 毫秒)

固定间隔示例

如果我们重新创建之前的 “月份” calendar_interval,我们可以用 30 天估算:

POST /sales/_search?size=0
{
    "aggs" : {
        "sales_over_time" : {
            "date_histogram" : {
                "field" : "date",
                "fixed_interval" : "30d"
            }
        }
    }
}

但是如果我们尝试使用一个如星期这样的不支持的日历单位就会引发异常:

POST /sales/_search?size=0
{
    "aggs" : {
        "sales_over_time" : {
            "date_histogram" : {
                "field" : "date",
                "fixed_interval" : "2w"
            }
        }
    }
}
{
  "error" : {
    "root_cause" : [...],
    "type" : "x_content_parse_exception",
    "reason" : "[1:82] [date_histogram] failed to parse field [fixed_interval]",
    "caused_by" : {
      "type" : "illegal_argument_exception",
      "reason" : "failed to parse setting [date_histogram.fixedInterval] with value [2w] as a time value: unit is missing or unrecognized",
      "stack_trace" : "java.lang.IllegalArgumentException: failed to parse setting [date_histogram.fixedInterval] with value [2w] as a time value: unit is missing or unrecognized"
    }
  }
}

笔记

任何情况下,当指定的结束时间不存在时,实际结束时间是最接近指定结束时间的可用时间。

分布广泛的应用程序还应该考虑多变场景,比如某些国家夏令时间开始 / 结束于凌晨 12:01 ,因此每年结束都伴随着 59 分钟周六 和 1 分钟周日,再比如一些国家是被国际换日线穿过。上述情况很容易导致不规则时区偏移。

通常,严格的测试能够确保你的时间间隔规范符合你的预期,尤其是关于时间改变事件的测试。

警告:为了避免意外结果,所有建立连接的客户端和服务器,都应该和一个可靠的网络时间服务保持同步。

注意
不支持小数时间,但是你可以通过转换成其它时间单位解决(例如: 1.5h 可以用 90m 代替)。

注意
你可以使用 时间单位 支持的缩写来指定时间。

在内部,日期被表示为一个从纪元时间(1970.01.01 00:00:00 UTC)开始的 64 位的毫秒时间戳。时间戳被作为桶的 key 键名返回。 key_as_string 是一个符合 format 参数规范的格式化日期字符串,由同一时间戳转换来。

提示
如果你未指定 format,则使用字段映射中指定的第一个日期格式

POST /sales/_search?size=0
{
    "aggs" : {
        "sales_over_time" : {
            "date_histogram" : {
                "field" : "date",
                "calendar_interval" : "1M",
                "format" : "yyyy-MM-dd" 
            }
        }
    }
}
  1. 支持丰富的日期格式

Response:

{
    ...
    "aggregations": {
        "sales_over_time": {
            "buckets": [
                {
                    "key_as_string": "2015-01-01",
                    "key": 1420070400000,
                    "doc_count": 3
                },
                {
                    "key_as_string": "2015-02-01",
                    "key": 1422748800000,
                    "doc_count": 2
                },
                {
                    "key_as_string": "2015-03-01",
                    "key": 1425168000000,
                    "doc_count": 2
                }
            ]
        }
    }
}
时区

日期时间在 Elasticsearch 中使用 UTC 存储。默认所有存桶和范围查询也是使用 UTC 完成。如果想要使用 time_zone 参数来区分桶,应该设置一个不同的时区。

你既可以将时区指定为带偏移量的 ISO 8601 UTC (例如 +01:00 或 -08:00),也可以指定为 IANA 时区中的一个时区 ID,例如 America/Los_Angeles 。

看一下下面的例子:

PUT my_index/_doc/1?refresh
{
  "date": "2015-10-01T00:30:00Z"
}

PUT my_index/_doc/2?refresh
{
  "date": "2015-10-01T01:30:00Z"
}

GET my_index/_search?size=0
{
  "aggs": {
    "by_day": {
      "date_histogram": {
        "field":     "date",
        "calendar_interval":  "day"
      }
    }
  }
}

如果你未指定时区,则默认使用 UTC。
下面命令会将两个文件放到同一天的桶中,这些桶从 2015 年 10 月 1 日午夜开始。

{
  ...
  "aggregations": {
    "by_day": {
      "buckets": [
        {
          "key_as_string": "2015-10-01T00:00:00.000Z",
          "key":           1443657600000,
          "doc_count":     2
        }
      ]
    }
  }
}

如果您将 time_zone 指定为 -01:00,则该时区中的午夜比 UTC 时区的午夜提前 1 小时:

GET my_index/_search?size=0
{
  "aggs": {
    "by_day": {
      "date_histogram": {
        "field":     "date",
        "calendar_interval":  "day",
        "time_zone": "-01:00"
      }
    }
  }
}

第一个文档存入时间为 2015 年 9 月 30 日的桶,而第二个文档存入时间为 2015 年 10 月 1 日的桶:

{
  ...
  "aggregations": {
    "by_day": {
      "buckets": [
        {
          "key_as_string": "2015-09-30T00:00:00.000-01:00", 
          "key": 1443574800000,
          "doc_count": 1
        },
        {
          "key_as_string": "2015-10-01T00:00:00.000-01:00", 
          "key": 1443661200000,
          "doc_count": 1
        }
      ]
    }
  }
}
  1. key_as_string 值表示指定时区中每天午夜时间。

警告
使用会随着 DST (夏令时) 更改的时区时,这些靠近发生变化的时间的桶的存储区大小可能与你使用的时间间隔所期望的略有不同。举例说明,假设一个 DST 在 CET 时区开始:在 2016 年 3 月 27 日凌晨 2 点,时钟被调后 1 小时到当地时间凌晨 3 点。如果你以天为间隔,则当天的桶只有 23 个小时的数据,而不是其它桶存储的 24 个小时的数据。对于较短的时间间隔也是如此,比如 12 小时,在 3 月 27 日凌晨 DST 发生变化时,桶中也只有 11 个小时的数据。

偏移量

offset 参数通过指定正(+)或负(-)偏移持续时间来更改每个桶的起始值,比如 1h 代表 1 小时,1d 代表 1 天,更多可能的持续时间选项,请参见 时间单位

例如,当使用  作为间隔时,每个桶从午夜持续到午夜。将 偏移量 设置为 +6h ,每个桶的运行时间就变为从早上 6 点到第二天 早上 6 点。

PUT my_index/_doc/1?refresh
{
  "date": "2015-10-01T05:30:00Z"
}

PUT my_index/_doc/2?refresh
{
  "date": "2015-10-01T06:30:00Z"
}

GET my_index/_search?size=0
{
  "aggs": {
    "by_day": {
      "date_histogram": {
        "field":     "date",
        "calendar_interval":  "day",
        "offset":    "+6h"
      }
    }
  }
}

上面的请求会将文档分组到开始于上午 6 点的桶,而不是从午夜开始的一个单独的桶。

{
  ...
  "aggregations": {
    "by_day": {
      "buckets": [
        {
          "key_as_string": "2015-09-30T06:00:00.000Z",
          "key": 1443592800000,
          "doc_count": 1
        },
        {
          "key_as_string": "2015-10-01T06:00:00.000Z",
          "key": 1443679200000,
          "doc_count": 1
        }
      ]
    }
  }
}

注意
每个桶的开始 offset 是在 time_zone 调整完之后计算来的。

键响应

将 keyed 设置为 true 会将每个桶和一个唯一的字符串关联起来,并将范围作为散列值而不是数组返回。

POST /sales/_search?size=0
{
    "aggs" : {
        "sales_over_time" : {
            "date_histogram" : {
                "field" : "date",
                "calendar_interval" : "1M",
                "format" : "yyyy-MM-dd",
                "keyed": true
            }
        }
    }
}

响应结果:

{
    ...
    "aggregations": {
        "sales_over_time": {
            "buckets": {
                "2015-01-01": {
                    "key_as_string": "2015-01-01",
                    "key": 1420070400000,
                    "doc_count": 3
                },
                "2015-02-01": {
                    "key_as_string": "2015-02-01",
                    "key": 1422748800000,
                    "doc_count": 2
                },
                "2015-03-01": {
                    "key_as_string": "2015-03-01",
                    "key": 1425168000000,
                    "doc_count": 2
                }
            }
        }
    }
}
脚本

与正常的 直方图 一样,支持文档级别脚本和值级别脚本。你还可以使用 order 设置来控制返回桶的顺序,并可以通过 min_doc_count 设置对返回的桶进行过滤(默认情况下,与文档匹配的第一个桶和最后一个桶之间的所有桶都会被返回)。该直方图还支持 extended_bounds 设置,这使得直方图的范围扩展到数据本身之外(更多信息,请查看 扩展范围 )。

缺失值

missing 参数对如何处理文档缺失值进行了定义。默认情况下,它们会被忽略,但也可以认为它们有值。

POST /sales/_search?size=0
{
    "aggs" : {
        "sale_date" : {
             "date_histogram" : {
                 "field" : "date",
                 "calendar_interval": "year",
                 "missing": "2000/01/01" 
             }
         }
    }
  1. 在 publish_date 字段中没有值的文档将与值为 2000-01-01 的文档落入同一桶中。

排序

默认情况下,返回的桶按照他们的 key 升序排序,但是你可以通过 order 设置控制返回的顺序。该设置支持和 Terms Aggregation 同样的 order 功能。

使用脚本来按照星期几聚合

当你需要按照星期几来聚合时,使用如下脚本来返回结果:

POST /sales/_search?size=0
{
    "aggs": {
        "dayOfWeek": {
            "terms": {
                "script": {
                    "lang": "painless",
                    "source": "doc['date'].value.dayOfWeekEnum.value"
                }
            }
        }
    }
}

响应结果:

{
  ...
  "aggregations": {
    "dayOfWeek": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "7",
          "doc_count": 4
        },
        {
          "key": "4",
          "doc_count": 3
        }
      ]
    }
  }
}

响应中包含所有桶,它们使用每星期中的相对日作为键:1 代表星期一,2 代表星期二…… 7 代表星期日。