21、Metrics Aggregations

原文地址:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics.html

elasticsearch版本:6.5

目录地址:https://blog.csdn.net/mine_1/article/details/85623429

此系列中的聚合基于以某种方式从要聚合的文档中提取的值进行统计运算。这些值通常从文档的字段中提取(数值类型),您也可以使用脚本生成。

数值型聚合是一种特殊类型的聚合,它输出数值。一些聚合输出单个数字度量(例如avg),称为single-value numeric metrics aggregation,其他聚合生成多个度量(例如stats),称为multi-value numeric metrics aggregation。当这些聚合用作某些存储桶聚合的直接子聚合时,单值和多值数字度量聚合之间的区别起到了一定作用(某些存储桶聚合使您能够根据每个存储桶中的数字度量对返回的存储桶进行排序)。

目录

(1)Avg Aggregation

(2)Weighted Avg Aggregation

(3)Cardinality Aggregation

(4)Extended stats Aggregation

(5)Geo Bounds Aggregation

(6)Geo Centroid Aggregation

(7)Max Aggregation

(8)Min Aggregation

(9)Percentiles Aggregation

(10)Percentile Ranks Aggregation

(11)Scripted Metric Aggregation

(12)Stats Aggregation

(13)Sum Aggregation

(14)Top Hits Aggregation

(15)value Count Aggregation


(1)Avg Aggregation

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

假设一份文档中包含学生们的考试成绩,(从0到100),现在我们计算他们的平均分,如:

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

上面的聚合是就算所有文档中grade的平均值。聚合类型是avg,field指定需要聚合的数值型的字段。返回的结果为:

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

聚合结果的值存放在指定的字段中,如上例中是存放在avg_grade字段中。

<1>Script

就算平均分基于下面这个脚本:

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

这个请求将会被翻译成不带参数的painless格式的inline脚本。如果想要存储该脚本,使用如下语法:

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

<2>Value Script

结果证明,考试远高于学生的水平,需要进行分数修正。我们可以使用值脚本获取新的平均值:

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

<3>Missing value

missing参数可以设置文档的缺省值,当数值为空的时候我们可以选择忽略也可以设置missing值,如:

POST /exams/_search?size=0
{
    "aggs":{
        "grade_avg":{
            "avg":{
                "field":"grade",
                "missing":10
            }
        }
    }
}

没有grade值的文档和grade值为10的文档将属于同一个bucket.

(2)Weighted Avg Aggregation

单值聚合,计算文档中带权重的字段的平均值。这些值可以从文档中的数值型的字段中提取。当就算普通的平均值时,各个值有相同的权重值。带权平均值每个字段的权重不同。每个字段的权重由文档中的字段选择或是利用脚本指定。带权平均值的公式是:∑(value * weight) / ∑(weight)。普通的平均值可以看成是权重为1的带权平均值。

<1>Example

假设你的文档中有一个grade字段,其值为0到100的数值,还有一个weight字段,它规定了每个grade的权重,现在我们可以计算带权平均值,如:

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

得到的响应如:

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

多个统计字段是运行的,但是只能有一个权重字段。如果计算时发现多个权重值,将会返回错误异常。如果您需要这种情形,您需要制定一个script来指定权重。单个权重将会应用于需要计算的每个值。下面的例子展示了用一个权重字段计算一个文档中的多字段,如:

POST /exams/_doc?refresh
{
    "grade":[1,2,3],
    "weight":2
}
POST /exams/_search
{
    "szie":0,
    "aggs":{
        "weighted_grade":{
            "weighted_avg":{
                 "value":{
                    "field":"grade"
                  },
                  "weight":{
                     "field":"weight"
                  }
            }
        }
    }
}

1,2和3这三个值将会都按照权重2进行计算,得到的响应为:

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

聚合的结果为2.0,计算的公式为:

((1*2) + (2*2) + (3*2)) / (2+2+2) == 2

<2>script

字段和权重都可以用script指定,如下面的例子:

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

<3>Missing values

missing参数定义了当文档中缺失字段时的处理办法,value和weight的方式不同。默认情况下,如果缺失value字段的documents将会被忽略直接计算下一个文档,如果weight字段缺失,默认假设权重为1。两个的默认值都可以用missing参数自定义指定:

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

(3)Cardinality Aggregation

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

假设你有一份store sales数据,下面的查询将会统计出满足条件的不同的sold products的个数:

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

得到的响应为:

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

<1>精度控制

聚合计算支持precision_threshold选项,如:

POST /sales/_search?size=0
{
    "aggs":{
        "type_count":{
            "cardinality":{
                "field":"_doc",
                "precision_threshold":100
            }
        }
    }
}

注:precision_threshold参数允许用内存交换精确度,并定义了一个惟一的计数,在此计数之下,期望计数接近准确。在这个值之上,计数可能会变得更加模糊。支持的最大值是40000,超过这个阈值将会按照40000计算,默认值是3000。

<2>counts are approximate

计算精确计数需要将值加载到哈希集中并返回其大小。当处理high-cardinality sets和/或 large values时,由于所需的内存使用量和节点之间的每个碎片集的通信需求将占用集群的太多资源,这将会消过多的集群资源。

cardinality aggregation是一个近似计算。它是基于HyperLogLog++(HLL)算法,HLL先对我们的输入做哈希运算,然后根据哈希运算结果中的bits做概率估算从而得到基数。这个算法有以下的特性:

  • 可配置的精度,用来控制内存的使用(更精确=更多内存)
  • 小的数据集精度是非常高的
  • 我们可以通过配置参数,来设置去重需要的固定内存使用量。无论数千还是数十亿的唯一值,内存使用量只与我们配置的精确度相关。

要配置精度,我们必须制定precision——threshold参数,这个参数定义了在何种基数水平下我们希望得到一个近乎精确的结果。

HyperLogLog++算法依赖于哈希值的leading zeros,数据集中的哈希值的分布会影响基数的准确性。请注意,即使阈值低于100,在计算数百万条记录时,精确度仍然很高。

<3>Pre-computed hashes

string类型的字段具有high cardinality,将字段值的哈希值存储在索引中,然后再次字段上运行cardinality aggregation速度会更快。这可以通过client-side提供哈希值来实现,也可以通过使用mapper-murmur3插件为您计算哈希值。

注意:per-computing hashes只对内容很长或者基数很高的字段有用,计算这些字段的哈希值的消耗在查询时是无法忽略的。尽管数值型字段的哈希计算是非常快速的,存储它们的原始值通常需要同样或更少得内存空间。这对低基数的字符串字段同样适用,elasticsearch的内部优化能够保证每个唯一值只计算一次哈希。基本上说,预先计算并不能保证所有的字段都更快,它只对那些具有高基数和/或者内容很长的字符串字段有作用。需要记住的是,预计算只是简单的将查询消耗的时间提前转移到索引时,并非没有任何代价,区别在于你可以选择在什么时候做这件事,要么在索引时,要么在查询时。

要想这么做,我们需要为数据增加一个新的多只字段,我们先删除索引,在增加一个包括哈希值字段的映射,然后重新索引:

DELETE /cars/
PUT /cars/
{
    "mappings":{
        "transactions":{
            "properties":{
                "color":{
                    "type":"string",
                    "fields":{
                        "hash":{
                            "type":"murmur3"
                        }
                    }    
                }
            }
        }
    }
}

POST /cars/transactions/_bulk
{"index":{}}
{"price":10000,"color":"red","make":"handa","sold":"2014-10-28"}
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }

现在当我们执行聚合时,我们使用color.hash字段而不是color字段:

GET /cars/transactions/_search
{
    "size" : 0,
    "aggs" : {
        "distinct_colors" : {
            "cardinality" : {
              "field" : "color.hash" 
            }
        }
    }
}

现在cardianlity运算会读取“color.hash”里面的值,取代动态计算原始值的哈希。

<4>Script

cardinality支持脚本,但由于动态计算哈希值,性能可能会受到显著影响:

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

存储这个script如下:

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

<5>Missing value

missing参数定了当文档中没有值的时候的处理了方法。默认elasticsearch会忽略但是我们也可以赋给默认值,如:

POST /sales/_search?size=0
{
    "aggs":{
        "tag_cardinality":{
            "cardinality":{
                "field":"tag",
                "missing":"N/A"
            }
        }
    }
}

(4)Extended stats Aggregation

milti-value,计算从聚合文档中提取的数值的统计信息。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。extended_stats是stats的扩展版本,其中添加了额外的度量,如sum_of_squares、variance、std_deviation和std_deviation_bounds。

假设一份数据包含学生们的grades(0到100之间):

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

这个聚合计算所有文档的grades统计信息。统计类型是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
           }
        }
    }
}

<1>Standard Deviation Bounds

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

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

sigma可以设置为任何非负的double,如1.5。0也是合法的,如果设置为0将会返回upper和lower的平均值。

<2>Script

计算grades统计信息基于下面的script:

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

用下面的方法得到保存了的script:

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

<3>Value script

结果证明,考试远高于学生的水平,需要进行分数修正。我们可以使用值脚本获取新的统计信息:

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

<4>Missing value

missing参数定义了字段为空的文档的处理方法。默认情况下这些文档将会被忽略但是我们也可以给他们赋值,如:

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

(5)Geo Bounds Aggregation

此聚合计算包含所有满足条件的geo_point的box的边界,如:

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

POST /museums/_doc/_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 
            }
        }
    }

geo_bounds字段指定按照哪个字段获取范围,wrap_longitude是一个可选参数,指定范围是否允许与国际日界线重合。默认值是true。

得到的响应为:

{
    ...
    "aggregations":{
        "viewport":{
            "bounds":{
                "top_left":{
                    "lat":48.86111093862,
                    "lng":2.3266999679187
                },
                "bottom_right":{
                   "lat": 48.85999997612089,
                   "lon": 2.3363889567553997
                }    
            }
        }
    }
}

(6)Geo Centroid Aggregation

获取满足条件的所有点的坐标值加权的形心,如:

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

POST /museums/_doc/_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" 
            }
        }
    }
}

注意:选定的field必须是Geo_point datatype类型。

得到的响应为:

{
    ...
   "aggregations" : {
     "centroid" : {
       "location" : {
         "lat" : 51.00982963107526,
         "lon" : 3.9662130922079086
       },
       "count" : 6
     }
   }
}

当geo_centroid与子聚合联合使用的时候,可以获得更好的效果,如:

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

这个请求用geo_centeroid当成aggs的terms,可以查到每个城市的museums的形心,得到的响应如:

{
    ...
    "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
                    }
                 }
            ]
        }
    }
}

(7)Max Aggregation

单值聚合,返回指定的数值型字段的最大值。可以通过指定文档中的字段或是通过提供的script获得。

注:min和max aggregation操作返回的是double类型的值,因此,当结果大于2^53时,得到的结果是近似值。

计算所有文档price的最大值:

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

得到的响应为:

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

<1>script

max aggregation对应的script如下:

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

利用已经存储的script如下:

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

<2>value script

我们的文档里面prices是美元,但是我们想要计算欧元的最大值,假设现在的换算比例是1.2,我们可以利用下面的script得到欧元的最大值:

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

<3>Missing value

missing参数设置当文档没有值时的处理方法,默认情况下elasticsearch会忽略这些文档,但是我们也可以设置一个默认值,如:

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

(8)Min Aggregation

与max aggregation类似,此处省略翻译。

(9)Percentiles Aggregation

百分位数聚合,多值聚合,计算文档中一个或多个数值型字段的百分位数。这些字段可以从文档中指定字段也可以用提供的script赋值。

百分位数表示观察值的某个百分比出现的点。例如,95%是大于观察值95%的值。百分位数通常用于查找异常值。在正态分布中,0.13%和99.87%表示与平均值的三个标准差。任何超出三个标准差的数据通常被视为异常。当检索到一个百分位数范围时,它们可以用来估计数据分布,并确定数据是否是歪斜的、双峰的等。

假设您的数据包含文章的load times。对于管理员来说,平均和中间加载时间并不太有用。最大值可能很有趣,但很容易被一个缓慢的响应所扭曲。

下面的例子是计算响应load time的百分位数,选定计算的字段必须是数值型:

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_outlier" : {
            "percentiles" : {
                "field" : "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-725毫秒内加载,但偶尔会达到945-985毫秒。

通常,管理员只对异常值-极端百分位数感兴趣。我们可以只指定我们感兴趣的百分比(请求的百分比必须是0-100之间的值,包括0-100):

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_outlier" : {
            "percentiles" : {
                "field" : "load_time",
                "percents" : [95, 99, 99.9] 
            }
        }
    }
}

<1>Keyed Response

默认情况下,keyed标志设置为true,它将唯一的字符串键与每个bucket相关联,并将范围作为哈希而不是数组返回。将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
                }
            ]
        }
    }
}

<2>script

percentile聚合支持script。比如,假设我们的load times是milliseconds,但是我们想要计算秒的百分位数,我们可以利用下面的script:

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_outlier" : {
            "percentiles" : {
                "script" : {
                    "lang": "painless",
                    "source": "doc['load_time'].value / params.timeUnit", 
                    "params" : {
                        "timeUnit" : 1000   
                    }
                }
            }
        }
    }
}

可以用下面的语句调用已经存储的script:

GET latency/_search
{
    "size": 0,
    "aggs" : {
        "load_time_outlier" : {
            "percentiles" : {
                "script" : {
                    "id": "my_script",
                    "params": {
                        "field": "load_time"
                    }
                }
            }
        }
    }
}

<3>Percentiles are (usually) approximate

有需要不同的算法可以计算半分位数,简单的实现只是将所有值存储在一个排序数组中。要找到第50个百分点,只需找到

my_array[count(my_array) * 0.5]。显然,最简单的实现可扩展性差-排序后的数组会随着数据集中的值的数量线性增长。为了计算弹性搜索集群中潜在数十亿值的百分位数,需要计算近似百分位数。elasticsearch选用的是TDigest算法。

<4>Compression

求近似值的计算需要权衡内存使用情况和值的精确程度。我们可以利用compression参数指定:

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

TDigest算法使用许多“nodes”来近似百分位数-可用的nodes越多,与数据量成比例的精度(和较大的内存占用)就越高。compression参数将最大nodes数限制为20*compression。

因此,通过增加compression值,您可以增加百分位数的准确性,但需要消耗更多的内存。较大的压缩值也会使算法变慢,因为底层树数据结构的大小会增加,从而导致更昂贵的操作。默认compression为100。

nodes使用大约32字节的内存,因此在最坏的情况下(大量数据按顺序排序到达),默认设置将产生大约64kb的数据。在实践中,数据往往更随机,数据格式将使用更少的内存。

<5>HDR Histogram

HDR Histogram(High Dynamic Range Histogram)是一种可选的备选项,在计算延迟测量的百分位数时非常有用,因为它比T-Digest实现更快,并且需要权衡更大的内存占用。此实现维护一个固定的更糟的事例百分比错误(指定为有效位数)。这意味着,如果在histogram中以1微秒到1小时(3600000000微秒)的值记录数据,并将其设置为3个有效数字,则对于高达1毫秒的值,其值分辨率将保持为1微秒;对于maximum tracked value(1小时),其值将保持为3.6秒(或更好)。

HDR Histogram用法如下:

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 
                }
            }
        }
    }
}

number_of_significant_value_digits只能指定正数,不能是负数。

<6>Missing value

missing参数可以指定当文档中不存在指定的值的处理方法。默认情况下会忽略这些documents,但是我们也可以通过下面的方法给他们一个默认值:

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

(10)Percentile Ranks Aggregation

多值聚合,从聚合文档中提取数值计算百分位数进行排名显示,这些值可以指定为文档的列或是通过script提供。百分位等级表示低于某一数值的观测值的百分比。例如,如果一个值大于或等于观察值的95%,则称其处于第95百分位等级。

假设您有一份网站加载时间的数据,您可以得到结论:500ms内加载完成的占95%,600ms内加载完成的占99%。看下面的例子:

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

注意load_time字段必须是数值型,得到的响应如:

{
    ...
    "aggregations":{
        "load_time_ranks":{
            "value":{
                "500.0":55.00000001,
                "600.0":64.0
            }
        }
    }
}

<1>keyed response

通过设置keyed参数为true,将会返回key,value格式的数据,而不是数组,如:

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
                }
            ]
        }
    }
}

<2>script

percentile rank度量聚合支持script。比如如果您的load times是milliseconds但是我们想要按照seconds统计,那么我们可以这样:

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
                    }
                }
            }
        }
    }
}

利用一个已经保存的script如下:

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

<3>HDR Histogram与Percentiles Aggregation中相同

<4>Missing value与Percentiles Aggregation中相同

(11)Scripted Metric Aggregation

执行脚本来设置度量输出的聚合,如:

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.document.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"
            }
        }
    }
}

其中map_script是必须的。

这个聚合演示了如何使用脚本聚合计算销售和成本事务的总利润。得到的响应为:

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

<1>Allowed return types

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

  • primitive types
  • string
  • Map
  • Array

<2>scope of scripts

scripted metric aggregation在执行的过程中用下面的4个阶段的脚本:

  • init_script:在任何文档聚合操作之前执行,为聚合设置初始状态,如上面的例子,在init_script中为state对象创建了一个数组transactions。
  • map_script:在文档聚合过程中知执行一次。这个必须提供。如果没有combine_script,统计的结果需要存储在state对象中。在上面的例子中,map_script教程检查type字段的值。如果这个值是sale加加载transactions中,如果不是就乘以-1然后加到transactions中。
  • combine_script:当文档的聚合执行完成后执行一次。把每个分片返回的统计结果聚合起来,如果没有提供combine_script,combine_script将直接返回聚合变量。在上面的例子中,combine_script迭代transactions中存储的值,求和存储到profit中返回。
  • reduce_script:在所有的分片返回统计结果后执行一次。这个脚本可以访问每个分片上面的combine_script的返回值。如果reduce_script脚本没有指定,将会默认返回states信息。在上面的例子中,reduce_script通过profit变量迭代每个分片的值然后返回所有的聚合结果。

<3>Worked Example

假设您分布在两个分片上面的下来文档建立索引:

PUT /transactions/_doc/_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上。下面是每一步聚合过程中的具体内容:

  • Before init_script:初始化state为一个空对象:
"state" : {}
  • After init_script,在每个分片上都有个state的副本:

A分片上:

"state":{
    "transaction":[]
}

B分片上:

"state":{
    "transactions":[]
}
  • After map_script:每个分片分布返回指定的数值的集合,

A分片

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

B分片

"state":{
    "transactions":[-10, 130]
}
  • After combine_script:当得到文档数字的集合后,每个分片分片进行合并,假如进行求和,得到的结果为:

分片A:50

分片B:120

  • After reduce_script:返回每个分片聚合结果的states数组,如:
"states":[
    50,
    120
]

然后将数组中的数据合并,如求和,放到aggregations参数中返回:

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

<4>其他参数

可选参数params,包含init_script,map_script,combine_script中需要用到的对象,这个参数可以运行用户控制aggregation中的行为或是保存脚本之间的状态。如果没有指定,默认值是:

"params":{}

(12)Stats Aggregation

一个多值聚合,计算文档中数值型字段的stats。需要计算的内容可以通过指定文档中的字段或是script提供。stats的返回值中包括min,max,sum,count和avg。

假设您有一份学生的成绩数据(从0到100):

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

上面的请求将会计算所有文档中grades的统计信息。stats的字段需要是文档中的数值型字段,返回的结果为:

{
    ...

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

<1>Script

统计grades基于下面的script:

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

调用已经存储的script,如下:

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

<2>Value Script

如果得到的考试成绩远高于学生的水平需要修正,我们可以利用下面的script:

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

<3>Missing value 省略。

(13)Sum Aggregation

一个单值聚合,统计文档中指定的数值型字段的和。这个值可以用文档中的字段或script指定。

假设您一份sales的数据,现在我们可以统计所有满足条件的销售额的和,如:

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

得到的响应为:

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

<1>script,我们可以通过下面的脚本获得sales,

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

<2>value script,也可以用_value访问字段的值,如下面,将会统计所有hats的价格的平方和:

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

<3>missing value 省略

(14)Top Hits Aggregation

top_hits聚合跟踪正在聚合的最相关文档。此聚合器旨在用作子聚合器,以便每个bucket可以聚合最佳匹配的文档。Top_Hits聚合器可以有效地用于通过Bucket聚合器按特定字段对结果集进行分组。一个或多个bucket聚合器确定将结果集切片到哪个属性中。

<1>Options

  • from:您想获得的第一个结果的偏移量
  • size:每个bucket返回得top matching的个数,默认是返回3个
  • sort:如何排序来获得top matching,默认是按照main query的hits score排序

<2>supported per hit features,返回的结果支持如下特性:

<3>Example,下面的例子按照type分组,每个分组返回3个sales,每个sale在source中值包含date和price字段,如:

POST /sales/_search?szie=0
{
    "aggs":{
        "top_tags":{
            "term":{
                "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": 3,
                   "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": 3,
                   "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": 1,
                   "max_score": null,
                   "hits": [
                      {
                         "_index": "sales",
                         "_type": "_doc",
                         "_id": "AVnNBmatCQpcRyxw6ChH",
                         "_source": {
                            "date": "2015/01/01 00:00:00",
                            "price": 150
                         },
                         "sort": [
                            1420070400000
                         ],
                         "_score": null
                      }
                   ]
                }
             }
          }
       ]
    }
  }
}

<1>field collapse example

结果分组是一种功能,它可以将结果集按逻辑分组,每组返回top文档。组的顺序由组中第一个文档的相关性决定。在ElasticSearch中,这可以通过一个将top_hits聚合作为bucket聚合的子聚合器的来实现。

在下面的示例中,我们搜索已爬网的网页。对于我们存储的每个网页,包含主体和网页所属的域。通过在域字段上定义术terms聚合,我们按domain对网页的结果集进行分组。然后,top_hits被定义为子聚合器,以便每个bucket中收集top_hits。

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

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"
                        }
                    }
                }
            }
        }
    }
}

目前,需要使用max(或min)聚合来确保terms聚合中的存储桶是根据每个域中最相关网页的得分排序的。不幸的是,terms聚合的order选项中还不能使用top_hits聚合中。

(15)value Count Aggregation

单值聚合,返回文档中不同数值的个数。这个值可以文档中指定也可以通过script设置。一般情况下,这个聚合和其他的单值聚合结合使用。例如当我们计算type字段不同值的个数:

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

得到的响应为:

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

<1>Script,对应的脚本为:

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

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的ElasticSearch聚合的Java API示例: ```java import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.Client; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; import org.elasticsearch.search.aggregations.bucket.histogram.Histogram; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.metrics.sum.Sum; import org.elasticsearch.search.aggregations.metrics.valuecount.ValueCount; import static org.elasticsearch.index.query.QueryBuilders.rangeQuery; public class ElasticSearchAggregationExample { public static void main(String[] args) { // 创建ElasticSearch客户端 Client client = // ...; // 构建查询条件 QueryBuilder query = QueryBuilders.boolQuery() .must(rangeQuery("timestamp").gte("2022-01-01T00:00:00").lte("2022-01-31T23:59:59")); // 构建聚合条件 AggregationBuilder aggregation = AggregationBuilders .dateHistogram("sales_over_time") .field("timestamp") .dateHistogramInterval(DateHistogramInterval.DAY) .subAggregation( AggregationBuilders .terms("product_types") .field("product_type") .subAggregation( AggregationBuilders.sum("total_sales").field("sales"), AggregationBuilders.count("transaction_count").field("transaction_id") ) ); // 执行查询 SearchResponse response = client.prepareSearch("my_index") .setQuery(query) .addAggregation(aggregation) .execute() .actionGet(); // 解析聚合结果 Histogram histogram = response.getAggregations().get("sales_over_time"); for (Histogram.Bucket bucket : histogram.getBuckets()) { System.out.println("Date: " + bucket.getKeyAsString()); Terms productTypes = bucket.getAggregations().get("product_types"); for (Terms.Bucket productType : productTypes.getBuckets()) { System.out.println("Product Type: " + productType.getKeyAsString()); Sum totalSales = productType.getAggregations().get("total_sales"); System.out.println("Total Sales: " + totalSales.getValue()); ValueCount transactionCount = productType.getAggregations().get("transaction_count"); System.out.println("Transaction Count: " + transactionCount.getValue()); } } // 关闭客户端 client.close(); } } ``` 这个示例通过ElasticSearch的Java API执行了一个聚合,其中包含了两层嵌套聚合,分别按照日期和产品类型对销售数据进行了汇总,输出了每个日期和产品类型的销售总额和交易次数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值