Elasticsearch 5: 聚集查询

Elasticsearch 1: 基本原理和概念
Elasticsearch 2: 管理索引和文档
Elasticsearch 3: 数据检索和分析
Elasticsearch 4: 相关性检索和组合查询
Elasticsearch 5: 聚集查询
Elasticsearch 6: 索引别名
Elasticsearch 集群
SpringBoot 整合 Elasticsearch

文中使用的数据可以参考Elasticsearch 数据检索和分析导入数据相关内容

1. 聚集查询

  • 聚集查询(Aggregation)提供了针对多条文档的统计运算功能,它不是针对文档本身内容的检索,而是要将它们聚合到一起运算某些方面的特征值。
  • 聚集查询与 SQL 语言中的聚集函数非常像,聚集函数在 Elasticsearch 中相当于是聚集查询的一种聚集类型。 比如在 SQL 中的 avg 函数用于求字段平均值, 而在 Elasticsearch 中要实现相同的功能可以使用 avg 聚集类型。
  • 聚集查询也是通过_search 接口执行,只是在执行聚集查询时使用的参数是 aggregations 或 aggs。所以_search 接口可以执行两种类型的查询,1 种是通过 query 参数执行 DSL,另一种则是通过 aggregations 执行聚集查询。
  • 这两种查询方式还可以放在一起使用, 执行逻辑是先通过 DSL 检索满足查询条件的文档,然后再使用聚集查询对 DSL 检索结果做聚集运算这一规则适用于本章列举的所有聚集查询。聚集查询有着比较规整的请求结构,具体格式如下:
{
    "aggregations/aggs": {
        "<聚集名称>": {
            "<聚集类型>": {
                <聚集体>
            }
            ………
            [,"aggregations/aggs" : ( [ <子聚集>]+ ) ]
        }
       [,"<聚集名称>: (...) ]*
    }
}
  • aggregations 和 aggs 都是_search 的参数,其中 aggs 是 agregations 的简写。 每一个聚集查询都需要定义一个聚集名称,并归属于种聚集类型。聚集名称是用户自定义的, 而聚集类型则是由 Elasticsearch 预先定义好。
  • 聚集名称会在返回结果中标识聚集结果,而聚集类型则决定了聚集将如何运算。比如前面提到的 avg 就是一种聚集类型。 这里要特别强调的是,聚集中可以再包含子聚集。子聚集位于父聚集的名称中,与聚集类型同级,所以子聚集的运算都是在父聚集的环境中运算。Elasticsearch 对子聚集的深度没有限制,所以理论上说可以包含无限深度的子聚集。
  • 聚集类型总体上被分为四种大类型,即指标聚集(Metrics Aggregation)、 桶 型( Bucket Aggregation)、管道聚集( Pipeline Aggregation) 和矩阵聚集( Matrix Aggregation)。
  • 指标聚集是根据文档字段所包含的值运算某些统计特征值,如平均值、总和等,它们的结果一般都包含一个或多个数值,前面提到的 avg 聚集就是指标聚集 的 1 种。桶型聚集根据一定的分组标准将文档归到不同的组中,这些分组在 Elasticsearch 中被称为桶( Bucken),桶型聚集与 SQL 中 group by 的作用类似,一 般会与指标聚集嵌套使用。管道聚集可以理解为聚集结果的再聚集,它一般以另 一个聚集结果作为输人,然后在此基础上再做聚集。矩阵聚集由于是针对多字段做多种运算,所以形成的结果类似于矩阵。

2. 指标聚集

  • 指标聚集是根据文档中某一字段做聚集运算,比如计算所有产品销量总和、平均值等等。指标聚集的结果可能是单个值,这种指标聚集称为单值指标聚集;也可能是多个值,称为多值指标聚集。

2.1 平均值聚集

  • 平均值聚集是根据文档中数值类型字段计算平均值的聚集查询,包括 avg 聚 集和 weighted_avg 聚集两种类型。avg 聚集直接取字段值后计算平均值,而 weighted _avg 聚集则会在计算平均值时添加不同的权重。

2.1.1 avg 聚集

  • avg 聚集计算平均值,例如在示例中计算航班的平均延误时间:
POST /kibana_sample_data_flights/_search
{
    "aggs": {
        "delay_avg": {
            "avg": {
                "field": "FlightDelayMin"
            }
        }
    }
}

在这里插入图片描述

  • 在查询结果最下面可以找到延误时间,如果希望过滤掉其他字段,可以使用请求参数 fiter _path
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "delay_avg": {
            "avg": {
                "field": "FlightDelayMin"
            }
        }
    }
}

在这里插入图片描述

  • 在返回结果中,aggregations 是关键字,代表这是聚集查询的结果。其中的 delay_avg 则是在聚集查询中定义的聚集名称,value 是聚集运算的结果。
  • 在示例中运算航班延误时间时会将所有文档都包含进来做计算,如果只想其中一部分文档参与运算则可以使用 query 参数以 DSL 的形式定义查询条件。
  • 例如只计算飞往中国的航班平均延误时间
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "query": {
        "match": {
            "DestCountry": "CN"
        }
    },
    "aggs": {
        "delay_avg": {
            "avg": {
                "field": "FlightDelayMin"
            }
        }
    }
}

在这里插入图片描述

  • 在示例中请求_search 接口时,同时使用了 query 与 aggs 参数。在执行检索时会先通过 query 条件过滤文档,然后再在符合条件的文档中运算平均值聚集。
  • weighted_avg 无非就是根据某些条件对进行聚集的数据进行加权运算,和 avg 聚集没有本质差别。

2.2 计数聚集与极值聚集

  • 计数聚集用于统计字段值的数量,而极值聚集则是查找字段的极大值和极小值

2.2.1 计数聚集

  • value_count 聚集和 cardinality 聚集可以归入计数聚集中,前者用于统计从字段中取值的总数,而后者则用于统计不重复数值的总数。例如:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "country_code": {
            "cardinality": {
                "field": "DestCountry"
            }
        },
        "total_country": {
            "value_count": {
                "field": "DestCountry"
            }
        }
    }
}

在这里插入图片描述

  • 在示例中,cardinality 聚集统计了 DestCountry 字段非重复值的数量,类似于 SQL 中的 distinct。value_count 聚集则统计了 DestCountry 字段所有返回值的数量,类似于 SQL 中的 count。
  • 需要注意的是,cardinality 聚集的算法使用极小内存实现统计结果的基本准确。所以 cardinality 在数据量极大的情况下是不能保证完全准确的。

2.2.2 极值聚集

  • 极值聚集是在文档中提取某一字段最大值或最小值的聚集,包括 max 聚集 和 min 聚集。
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "max_price": {
            "max": {
                "field": "AvgTicketPrice"
            }
        },
        "min_price": {
            "min": {
                "field": "AvgTicketPrice"
            }
        }
    }
}

在这里插入图片描述

  • 聚集有 max_price 和 min_price 两个,它们分别计算了机票价格的最大值和最小值。

2.3 统计聚集

  • 统计聚集是一个多值指标聚集,也就是返回结果中会包含多个值,都是一些与统计相生的数据。统计聚集包含 stats 聚集和 extended_stats 聚集两种,前者返回的统计数据是一些比较基本的数值,而后者则包含一些比较专业的统计数值。

2.3.1 stats 聚集

  • stats 聚集返回结果中包括字段的最小值(min)、 最大值(max)、总和(sum)、 数量(count) 及平均值(avg) 五项内容。
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "price_stats": {
            "stats": {
                "field": "AvgTicketPrice"
            }
        }
    }
}

在这里插入图片描述

2.3.2 extended_stats 聚集

  • extended_stats 聚集增加了几项统计数据,这包括平方和、方差、标准方差 和标准方差偏移量。从使用的角度来看,extended_stats 聚集与 stats 聚集完全相同,只是聚集类型不同。
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "price_stats": {
            "extended_stats": {
                "field": "AvgTicketPrice"
            }
        }
    }
}

在这里插入图片描述

2.4 百分位聚集

  • 百分位聚集根据文档字段值统计字段值按百分比的分布情况,包括 pecrentiles 聚集和 percentile_ranks 两种。前者统计的是百分比与值的对应关系, 而后者正好相反统计值与百分比的对应关系。
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "price_percentile": {
            "percentiles": {
                "field": "AvgTicketPrice",
                "percents": [
                    25,
                    50,
                    75,
                    100
                ]
            }
        },
        "price_percentile_rank": {
            "percentile_ranks": {
                "field": "AvgTicketPrice",
                "values": [
                    600,
                    1200
                ]
            }
        }
    }
}

在这里插入图片描述

  • pecrentiles 聚集通过 pecrents 参数设置组百分比,然后按值由小到大的顺序划分不同区间,每个区间对应一个百分比。percentile_ranks 聚集则通过 value 参数设置组值, 然后根据这些值分别计算落在不同值区间的百分比。
  • 以示例返回的结果为例: 在 pecrentiles 返回结果 price_percentile 中,“25. 0” : 410. 0127977258341 代表的含义是 25%的机票价格都小于 410. 0127977258341,其他以此类推。在 percentile_ranks 返回结果 price_percentile_rank 中, “600.0”: 45. 39892372745635 代表的含义是 600.0 以下的机票占总机票价格的百分比为 45.39892372745635%。

3. 使用范围分桶

  • 如果使用 SQL 语言类比,桶型聚集与 SQL 语句中的 group by 子句极为相似。 桶型聚集(Bucket Aggregation)是 Elasticsearch 官方对这种聚集的叫法,它起的作用是根据条件对文档进行分组。
  • 可以将这里的桶理解为分组的容器,每个桶都与一个分组标准相关联,满足这个分组标准的文档会落桶中。所以在默认情况下,桶型聚集会根据分组标准返回所有分组,同时还会通过 doc_count 字段返回每一桶中的文档数量。
  • 由于单纯使用桶型聚集只返回桶内文档数量,意义并不大,所以多数情况下都是将桶型聚集与指标聚集以父子关系的形式组合在起使用。桶型聚集作为父聚集起到分组的作用。而指标聚集则以子聚集的形式出现在桶型聚集中, 起到分组统计的作用。比如将用户按性别分组,然后统计他们的平均年龄。
  • 按返回桶的数量来看,桶型聚集可以分为单桶聚集和多桶聚集。在多桶聚集中,有些桶的数量是固定的。而有些桶的数量则是在运算时动态决定。由于桶聚 集基本都是将所有桶一次返回,返回了过多的通会影响性能,所以单个请求允许 返间的最大桶数受 search.max_bucket 参数限制。
  • 这个参数在 7.0 之前的版本中默认值为-1,代表无上限。但在 Elasticsearch 版本 7 中,这个参数的默认值已经更改为 10000, 所以在做桶型聚集时要先做好数据验证,防止桶数量过多影响性能。

3.1 数值范围

  • range、date_range 与 ip_range 这三种类型的聚集都用于根据字段的值范围内对文档分桶,字段值在同一范围内的文档归入同一桶中。每个值范围都可通过 from 和 to 参数指定,范围包含 from 值但不包含 to 值,用数学方法表示就是[from, to)。 在设置范围时,可以设置一个也可以设置多个,范围之间并非一定要连续,可以有间隔也可以有重叠。

3.1.1 range 聚集

  • range 聚集使用 ranges 参数设置多个数值范围,使用 field 参数指定 1 个数值类型的字段。range 聚集在执行时会将该字段在不同范围内的文档数量统计出来, 并在返回结果的 doc_count 字段中展示出来。例如统计航班不同范围内的票价数量,可以按示例的方式发送请求:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "price_ranges": {
            "range": {
                "field": "AvgTicketPrice",
                "ranges": [
                    {
                        "to": 300
                    },
                    {
                        "from": 300,
                        "to": 600
                    },
                    {
                        "from": 600,
                        "to": 900
                    },
                    {
                        "to": 900
                    }
                ]
            }
        }
    }
}

在这里插入图片描述

  • 在返回结果中,每个范围都会包含一个 key 字段,代表了这个范围的标识, 它的基本格式是“from- to"。如果觉得返回的这种 key 格式不容易理解,可以通过在 range 聚集的请求中添加 key 参数定制返回结果的 key 字段值。

在这里插入图片描述

3.1.2 date_range 聚集

  • date_range 聚集与 range 聚集类似,只见范围和字段的类型为日期而非数值
  • date_range 聚集的范围指定也是通过 ranges 参数设置,具体的范围也是使 用from- to两个参数,并且可以使用 key 定义返回结果的标识。 date_range 聚集多了个指定日期格式的参数 format, 可以用于指定 from 和 to 的 目期格式。例如,
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "mar_flights": {
            "date_range": {
                "field": "timestamp",
                "ranges": [
                    {
                        "from": "2022-02-01",
                        "to": "2022-02-26"
                    }
                ],
                "format": "yyyy-MM-dd"
            }
        }
    }
}

在这里插入图片描述

3.1.3 ip_range 聚集

  • ip_ range 聚集根据 ip 类型的字段统计落在指定 IP 范围的文档数量,使用的聚集类型名称为 ip_ range。例如,统计了两个 IP 地址范围的文档数量:
POST /kibana_sample_data_logs/_search?filter_path=aggregations
{
    "aggs": {
        "local": {
            "ip_range": {
                "field": "clientip",
                "ranges": [
                    {
                        "from": "157.4.77.0",
                        "to": "157.4.77.255"
                    },
                    {
                        "from": "105.32.127.0",
                        "to": "105.32.127.255"
                    }
                ]
            }
        }
    }
}

在这里插入图片描述

3.2 间隔范围

  • histogram、date _ histogram 与 auto_date_histogram 这三种聚集与使用数值定义范围的聚集很像,也是统计落在某一范围内的文档数量。 但与数值范围聚集不同的是,这三类座集统计范围由固定的间隔定义,也就是范围的结束值和起始值的差值是固定的。

3.2.1 histogram 聚集

  • histogram 聚集以数值为间隔定义数值范围,字段值具有相同范围的文档将落入同桶中。例如示例以 100 为间隔做分桶,可以通过返回结果的 doc_count 字段获取票价在每个区间的文档数量:
POST /kibana_sample_data_flights/_search?filter_path=aggregations 
{
    "aggs": {
        "price_histo": {
            "histogram": {
                "field": "AvgTicketPrice",
                "interval": 100,
                "offset": 50,
                "keyed": false,
                "order": {
                    "_count": "asc"
                }
            }
        }
    }
}

在这里插入图片描述

  • 其中,interval 参数用于指定数值问隔必须为正值,而 offset 参数则代表起 始数值的偏移量,必须位于[0, interval) 范围内。order 参数用于指定排序字段和顺序,可选字段为_key 和_count。当 keyed 参数设置为 true 时,返回结果中每 个桶会有一个标识,标识的计算公式为
bucket_key = Math. floor( ( value- offset)/interval) * interval + offset

3.2.2 date_histogram 聚集

  • date_histogra 聚集以时间为间隔定义日期范围,字段值具有相同日期范围的文档将落入同一桶中。同样,返回结果中也会包含每个间隔范围内的文档数量 doc_count。 例如统计每月航班数量:
POST /kibana_sample_data_flights/_search?filter_path=aggregations 
{
    "aggs": {
        "month flights": {
            "date_histogram": {
                "field": "timestamp",
                "interval": "month"
            }
        }
    }
}

在这里插入图片描述

  • 在示例使用参数 interval 指定时间间隔为 month,即按月划分范围。时间可以是还有:
    毫秒:1ms 10ms
    秒:second/1s 10s
    分钟:minute/1m 10m
    小时:hout/1h 2h
    天:day 2d 不支持
    星期:week/1w 不支持
    月:month/1M 不支持
    季度:quarter/1q 不支持
    年:year/1y 不支持
  • 不过这个参数将要过期,替代的是 fixed_interval 和 calendar_interval
  • 简单来说,calendar_interval 支持 1 年、1 季度、1 月、1 周、1 天、1 小时、 1 分钟,不支持时间为复数,fixed_interval 支持的时间单位为天、小时、分、秒、 毫秒,允许复数,例如"fixed_interval" : “30d”,表示为 30 天。

3.2.3 auto_date_histogram 聚集

  • 前述两种聚集都是指定间隔的具体值是多少,然后再根据间隔值返回每一 桶中满足条件的文档数。最终会有多少桶取决于两个条件,即间隔值和字段值在所有文档中的实际跨度。反过来,如果预先指定需要返回多少个桶,那么间隔值也可以通过桶的数量以及字段值跨度共同确定。auto_date_histogram 聚集就是这样一种聚集,它不是指定时间间隔值,而是指定需要返回桶的数量。例如在示例中定义需要返回 10 个时间段的桶
POST /kibana_sample_data_flights/_search?filter_path=aggregations 
{
    "aggs": {
        "age_group": {
            "auto_date_histogram": {
                "field": "timestamp",
                "buckets": 10
            }
        }
    }
}

在这里插入图片描述

  • 参数 field 设置通过哪一个字段做时间分隔,而参数 buckets 则指明了需要返回多少个桶。 默认情况下, buckets 的数量为 10。需要注意的是,buckets 只是设置了期望返回桶的数量,但实际返回桶的数量可能等于也可能小于 buckets 设置的值。例如示例的请求中期望 10 个桶,但实际可能只返回 6 个桶。
  • auto_date_histogram 聚集在返回结果中还提供了一个 interval 字段,用于说明实际采用的间隔时间。从实现的角度来说,不精确匹配 buckets 数量也有利于提升检索的性能

3.3 子聚集 (聚集嵌套)

  • 前面的桶型聚集,大部分都只是返回满足聚集条件的文档数量。在实际应用中,如果需要桶型聚集与 SQL 中的 group by 具有相同的意义,用于将文档分桶后计算各桶特定指标值,比如根据用户性别分组,然后分别求他们的平均年龄。Elasticsearch 这样的功能通过子聚集 (聚集嵌套)来实现。例如,示例中的请求就是先按月对从中国起飞的航班做了分桶,然后又通过聚集嵌套计算每月平均延误时间:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "query": {
        "term": {
            "OriginCountry": "CN"
        }
    },
    "aggs": {
        "date_price_histogram": {
            "date_histogram": {
                "field": "timestamp",
                "interval": "month"
            },
            "aggs": {
                "avg_price": {
                    "avg": {
                        "field": "FlightDelayMin"
                    }
                }
            }
        }
    }
}

在这里插入图片描述

  • 在示例中,_search 接口共使用了两个参数,query 参数以 term 查询条件将所有 OriginCountry 字段是 CN 的文档筛选出来参与聚集运算。
  • aggs 参数则定义了一个名称为 data_price_histogram 的桶型聚集,这个聚集内部又嵌套了个名称为 avg_ price 的聚集。由于 price 这个聚集位于 data_price histogram 中,所以它会使用这个聚集的分桶结果做运算而不会针对所有文档。 所以,最终的效果就是将按月计算从中国出发航班的平均延误时间。
  • 使用嵌套聚集时要注意,嵌套聚集应该位于父聚集名称下而与聚集类型同级,并且需要通过参数再次声明。如果与父聚集一样位于 aggs 参数下,那么这两个聚集就是平级而非嵌套聚集。

4. 使用词项分桶

  • 使用字段值范围分桶主要针对结构化数据,比如年龄、IP 地址等等。但对于字符串类型的字段来说,使用值范围来分桶显然是不合适的。由于字符串类型字段在编入索引时会通过分析器生成词项,所以字符申类型字段的分桶一般通过词项实现。使用词项实现分桶的聚集,包括 terms、significant_terms 和 significant_text 聚集。由于使用词项分桶需要加载所有词项数据,所以它们在执行速度上都会比较慢。为了提升性能,Elsticesearch 提供了 sampler 和 diversifed_sampler 聚集,可通过缩小样本数量减少运算量

4.1 terms 聚集

  • terms 聚集根据文档字段中的词项做分桶,所有包含同一词项的文档将被归人同一桶中,聚集结果中包含字段中的词项及其词频,在默认情况下还会根据词频排序,所以 terms 聚集也可用于热词展示,由于 terms 聚集在统计词项的词频数据时需要打开它的 fielddata 机制。fielddata 机制对内存消耗较大且有导致内存溢出的可能, 所以 terms 聚集一般针对 keyword 非 text 类型。
  • Fielddata:其实根据倒排索引反向出来的一个正排索引,即 document 到 term 的映射。
  • 只要我们针对需要分词的字段设置了 fielddata,就可以使用该字段进行聚合,排序等。我们设置为 true 之后,在索引期间,就会以列式存储在内存中。为什么存在于内存呢,因为按照 term 聚合,需要执行更加复杂的算法和操作,如果基于磁盘或者 OS 缓存,性能会比较差。
  • fielddata 堆内存要求很高,如果数据量太大,对于 JVM 来及回收来说存在 一定的挑战,也就是对 ES 带来巨大的压力。所以 doc_value 的出现我们可以使用磁盘存储,他同样是和 fielddata 一样的数据结构,在倒排索引基础上反向出来的正排索引,并且是预先构建,即在建倒排索引的时候,就会创建 doc values。, 这会消耗额外的存储空间,但是对于 JVM 的内存需求就会减少。总体来看, DocValues 只是比 fielddata 慢一点,大概 10-25%,则带来了更多的稳定性。
  • cardlinality 聚集可以统计字段中不重复词项的数量,而 terms 聚集则可以将这些词项全部展示出来。与 cardlinality 聚集一样, terms 聚集统计出来的词频也不能保证完全精确。例如:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "country_terms": {
            "terms": {
                "field": "DestCountry",
                "size": 10
            }
        },
        "country_terms_count": {
            "cardinality": {
                "field": "DestCountry"
            }
        }
    }
}

在这里插入图片描述
在这里插入图片描述

  • 在示例中定义了两个聚集,由于它们都是定义在 aggs 下,所以不是嵌套聚集。terms 聚集的 field 参数定义了提取词项的字段为 DestCountry, 它的词项在 返回结果中会按词频由高到低依次展示,词频会在返回结果的 doc_count 字段中展示。另一个参数 size 则指定了只返回 10 个词项,这相当把 DestCountry 字段中词频前 10 名检索出来。

4.2 significant_terms 聚集

  • terms 聚集统计在字段中的词项及其词频,聚集结果会按各词项总的词频排序,并将出现次数最多的词项排在最前面,这非常适合做推荐及热词类的应用。但按词频总数不一定总是正确的选择,在一些检索条件已知的情况下,一些词频总数比较低的词项反而是更合适的推荐热词。
  • 举例来说,假设在 10000 篇技术类文章的内容中提到 Elasticsearch 有 200 篇, 占比为 2%;但在文章标题含有 NoSQL 的 1000 篇文章中,文章内容提到 Elasticsearch 的为 180 篇,占比为 18%。 这种占比显著的提升,说明在文章标题含有 NoSQL 的条件下,Elasticsearch 变得更为重要。换句话说,如果一个词项在某个文档子集中与在文档全集中相比发生了非常显著的变化,就说明这个词项在这个文档子集中是更为重要的词项。
  • significant_terms 聚集就是针对上述情况的一种聚集查询,它将文档和词项分为前景集 Foreground Set 和背景集 Background Set。前景集对应一个文档子集, 背景集则对应文档全集。significant_terms 聚集根据 query 指定前景集,运算 field 参数指定字段中的词项在前景集和背景集中的词频总数,并在结果的 doc_coumt 和 bg_coumt 中保存它们。例如:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "query": {
        "term": {
            "OriginCountry": {
                "value": "IE"
            }
        }
    },
    "aggs": {
        "dest": {
            "significant_terms": {
                "field": "DestCountry"
            }
        }
    }
}
  • 在示例中,query 参数使用 DSL 指定了前景集为出发国家为 IE (即爱尔兰)的航班,而聚集查询中则使用 significant_ terms 统计到达国家的前景集词频和背景集词频。来看下返回结果

在这里插入图片描述

  • 在返回结果中,前景集文档数量为 119,背景集文档数量为 13059。
  • 在 buckets 返回的所有词项中,国家编码为 GB 的航班排在第一位。它在前景集中的词频为 12,占比约为 10% (12/119); 而在背景集中的词频为 449,占比约为 3. 4% (445/13059)。
  • 词项 GB 在前景集中的占比是背景集中的 3 倍左右,发生了显著变化,所以在这个前景集中 GB 可以被视为热词而排在第一位。GB 代表的国家是英国,从爱尔兰出发去英国的航班比较多想来也是合情合理的。
  • 除了按示例方式使用 query 参数指定前景集以外,还可以将 terms 聚集与 significant_terms 聚集结合起来使用,这样可以一次性列出一个字段的所有前景集的热词。例如:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "orgin_dest": {
            "terms": {
                "field": "OriginCountry"
            },
            "aggs": {
                "dest": {
                    "significant_terms": {
                        "field": "DestCountry"
                    }
                }
            }
        }
    }
}

在这里插入图片描述

  • 在示例中,使用 terms 聚集将 OriginCountry 字段的词项全部查询出来做前景集,然后再与 significant_terms 聚集起查询它们的热词。

4.3 significant_text 聚集

  • 如果参与 significant_terms 聚集的字段为 text 类型,那么需要将字段的 fielddata 机制开启,否则在执行时会返回异常信息。significant_text 聚集与 significant_terms 聚集的作用类型,但不需要开启字段的 fielddata 机制,所以可以把它当成是种专门为 text 类型字段设计的 significant_terms 聚集。例如在 kibana_sample_data_logs 中,message 字段即为 text 类型,如果想在这个字段上做词项分析就需要使用 significant_terms 聚集:
POST /kibana_sample_data_logs/_search?filter_path=aggregations
{
    "query": {
        "term": {
            "response": {
                "value": "200"
            }
        }
    },
    "aggs": {
        "agent_term": {
            "significant_text": {
                "field": "message"
            }
        }
    }
}
  • 在示例中,前景集为响应状态码 response 为 200 的日志,significant_text 聚 集则查看在这个前景集下 message 字段中出现异常热度的词项。返回结果片段:

在这里插入图片描述

  • 通过展示的返回结果可以看出,排在第一位的词项 200 在前景集和背景集中的数量是一样的, 这说明 message 中完整地记录了 200 状态码;而排在第二位的词项 beats 前景集和背景集分别为 3462 和 3732。这说明请求“/beats" 地址的成功率要远高于其他地址
  • significant_text 聚集之所以不需要开启fielddata机制是因为它会在检索时对 text 字段重新做分析,所以 significant_text 聚集在执行时速度比其他聚集要慢很多。如果希望提升执行效率,则可以使用 sampler 聚集通过减少前景集的样本数量降低运算量。

4.4 样本聚集

  • sampler 聚集的作用是限定其内部嵌套聚集在运算时采用的样本数量。 sampler 提取样本时会按文档检索的相似度排序,按相似度分值由高到低的顺序 提取。例如:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "query": {
        "term": {
            "OriginCountry": {
                "value": "IE"
            }
        }
    },
    "aggs": {
        "sample_data": {
            "sampler": {
                "shard_size": 100
            },
            "aggs": {
                "dest_country": {
                    "significant_terms": {
                        "field": "DestCountry"
                    }
                }
            }
        }
    }
}

在这里插入图片描述

  • 在示例中共定义了 sample_ data 和 dest_country 两个聚集,其中 dest_country 是 sample_data 聚集的子聚集或嵌套聚集,因此 dest_country 在运算时就只从分片上取一部分样本做运算。sampler 聚集的 shard_size 就是定义了每个分片上提取样本的数量,这些样本会根据 DSL 查询结果的相似度得分由高到低的顺序提取。
  • 执行后会发现,这次目的地最热的目的地国家由 GB 变成了 KR,这就是样本范围缩小导致的数据失真。为了降低样本减少对结果准确性的影响,需要将些重复的数据从样本中剔除。换句话说就是样本更加分散,加大样本数据的多样性。 Elasticsearch 提供的 diversified_sampler 聚集提供了样本多样性的能力,它提供了 field 或 script 两个参数用于去除样本中可能重复的数据。由于相同航班的票价可能是相同的,所以可以将票价相同的航班从样本中剔除以加大样本的多样性,例如:
{
    "query": {
        "term": {
            "OriginCountry": {
                "value": "IE"
            }
        }
    },
    "aggs": {
        "sample_data": {
            "diversified_sampler": {
                "shard_size": 100,
                "field": "AvgTicketPrice"
            },
            "aggs": {
                "dest_country": {
                    "significant_terms": {
                        "field": "DestCountry"
                    }
                }
            }
        }
    }
}

在这里插入图片描述

  • diversified_sampler 通过 field 参数设置了 AvgTicketPrice 字段,这样在返回结果中 GB 就又重新回到了第一位。

5. 单桶聚集

  • 单桶聚集在返回结果中只会形成一个桶,它们都有比较特定的应用场最。在 Elasticsearch 中,单桶聚集主要包括 filter,global, missing 等几种类型。另外还有一种 filters 聚集,它虽然属于多桶聚集, 但与 filter 聚集很接近。

5.1 过滤器聚集

  • 过滤器聚集通过定义一个或多个过滤器来区分桶,满足过速器条件的文档将落入这个过滤器形成的桶中。过滤器聚集分为单桶和多桶两种,对应的聚集类型自然就是 filter 和 filters。
  • 看 filter 桶型聚集,它属于单桶型聚集。一般会同时嵌套一个指标聚集, 用于在过滤后的文档范围内计算指标,例如:
POST /kibana_sample_data_flights/_search?size=0&filter_path=aggregations
{
    "aggs": {
        "origin_cn": {
            "filter": {
                "term": {
                    "OriginCountry": "CN"
                }
            },
            "aggs": {
                "cn_ticket_price": {
                    "avg": {
                        "field": "AvgTicketPrice"
                    }
                }
            }
        },
        "avg_price": {
            "avg": {
                "field": "AvgTicketPrice"
            }
        }
    }
}

在这里插入图片描述

  • 在示例中一共定义了 3 个聚集,最外层是两个聚集,最后聚集为嵌套聚集,origin_cn 聚集为单过滤器的桶型聚集,它将所有 OriginCountry 为 CN 的文档归入一桶。
  • origin_cn 桶型聚集嵌套了 cn_ticket_price 指标聚集,它的作用是计算当前桶内文档 AvgTicketPrice 字段的平均值。另一个外层聚集 avg_price 虽然也是计算 AvgTicketPrice 字段的平均值,但它计算的是所有文档的平均值。实际上,使用 query 与 agg 结合起来也能实现类似的功能,区别在于过滤器不会做相似度计算,所以效率更高一些也更灵活一些。
  • 多过滤器与单过滤器的作用类似,只是包含有多个过滤器,所以会形成多个桶。多过滤桶型聚集使用 filters 参数接收过滤条件的数组,一般也是与指标聚集一同使用。例如使用两个过滤器计算从中国、美国出发的航班平均机票价格:
POST /kibana_sample_data_flights/_search?size=0&filter_path=aggregations
{
    "aggs": {
        "origin_cn_us": {
            "filters": {
                "filters": [
                    {
                        "term": {
                            "OriginCountry": "CN"
                        }
                    },
                    {
                        "term": {
                            "OriginCountry": "US"
                        }
                    }
                ]
            },
            "aggs": {
                "avg_ price": {
                    "avg": {
                        "field": "AvgTicketPrice"
                    }
                }
            }
        }
    }
}

在这里插入图片描述

  • 计算出发地是CN的桶和出发地是US的桶的平均票价

5.2 global 聚集

  • global 桶型聚集也是一种单桶型聚集, 它的作用是把索引中所有文档归入一个桶中。这种桶型聚集看似没有什么价值,但当 global 桶型聚集与 query 结合起来使用时,它不会受 query 定义的查询条件影响,最终形成的桶中仍然包含所有文档。global 聚集在使用上非常简单,没有任何参数,例如:
POST /kibana_sample_data_flights/_search?size=0&filter_path=aggregations
{
    "query": {
        "term": {
            "Carrier": {
                "value": "Kibana Airlines"
            }
        }
    },
    "aggs": {
        "kibana_avg_delay": {
            "avg": {
                "field": "FlightDelayMin"
            }
        },
        "all flights": {
            "global": {
                
            },
            "aggs": {
                "all_avg_delay": {
                    "avg": {
                        "field": "FlightDelayMin"
                    }
                }
            }
        }
    }
}

在这里插入图片描述

  • 在示例中 query 使用 term 查询将航空公司为“Kibana Airline"的文档都检索出来,而 kibana_avg_ delay 定义的平均值聚集会将它们延误时间的平均值计算出来。但另个 all fights 聚集由于使用了 global 聚集,所以在嵌套的 all_avg_delay 聚集中计算出来的是所有航班廷误时间的平均值。

5.3 missing 聚集

  • missing 聚集同样也是一种单桶型聚集,它的作用是将某一字段缺失的文档归入一桶。
  • missing 聚集使用 field 参数定义要检查缺失的字段名称,例如:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "no_price": {
            "missing": {
                "field": "AvgTicketPrice"
            }
        }
    }
}

在这里插入图片描述

  • 示例将 kibana_sample_data_flights 中缺失 AvgTicketPrice 字段的文档归入一 桶,通过返回结果的 doc_count 查询数量也可以与指标聚集做嵌套,计算这些文档的某一指标值。

6. 聚集组合

  • 有两种比较特殊的多桶型聚集,它们是 composite 聚集和 adjacency_matrix 聚集。这两种聚集是以组合不同条件的形式形成新桶,只是在组合的方法和组件的条件上存在着明显差异。
  • composite 聚集可以将不同类型的聚集组合到一起,它会从不同的聚集中提取数据,并以笛卡尔乘积的形式组合它们,而每一个组合就会形成一个新桶。例如想查看平均票价与机场天气的对应关系,可以这样:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "price_weather": {
            "composite": {
                "sources": [
                    {
                        "avg_price": {
                            "histogram": {
                                "field": "AvgTicketPrice",
                                "interval": 500
                            }
                        }
                    },
                    {
                        "weather": {
                            "terms": {
                                "field": "OriginWeather"
                            }
                        }
                    }
                ]
            }
        }
    }
}

在这里插入图片描述

  • 在示例中,composite 聚集中通过 soures 参数定义了两个需要组合的子聚集。 第一个聚集 avg_price 是一个针对 AvgTicketPrice 以 500 为间隔的 histogam 聚集, 第二个则聚集 weather 则一个针对 OriginWeather 的 terms 聚集。sources 参数中还可以定义更多的聚集,它们会以笛卡儿乘积的形式组合起来。
  • 在返回结果中除了由各聚集组合形成的桶以外,还有一个 after_key 字段,
  • 它包含自前聚集结果中最后一个结果的 key。所以请求下一页聚集结果就可 以通过 after 和 size 参数值定,例如:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "price_weather": {
            "composite": {
                "after": {
                    "avg_price": 500.0,
                    "weather": "Cloudy"
                },
                "sources": [
                    {
                        "avg_price": {
                            "histogram": {
                                "field": "AvgTicketPrice",
                                "interval": 500
                            }
                        }
                    },
                    {
                        "weather": {
                            "terms": {
                                "field": "OriginWeather"
                            }
                        }
                    }
                ]
            }
        }
    }
}

在这里插入图片描述

  • adjacency_matrix 又叫邻接矩阵,是图论中的概念,描述顶点之间的相邻关系,adjacency_matrix 聚集因为牵涉到这些概念,略过,感兴趣的同学可以自行研究

7. 管道聚集

  • 管道聚集不是直接从索引中读取文档,而是在其他聚集的基础上再进行聚集运算。所以管道聚集可以理解为是在聚集结果上再次做聚集运算,比如求聚集结果中多个桶中某一指标的平均值、最大值等。要实现这样的目的,管道聚集都会包含一个名为 buckets_path 的参数,用于指定访问其他桶中指标值的路径。 buckets_ path 参数的值由三部分组成,即聚集名称、指标名称和分隔符。
  • 聚集名称与聚集名称之间的分隔符是“>”,而聚集名称与指标名称之间的分隔符使用“.”。
  • 按管道聚集运算来源分类,管道聚集可以分为基于父聚集结果和基于兄弟聚集结果两类。前者使用父聚集的结果并将运算结果添加到父聚集结果中,后者则使用兄弟聚集的结果并且结果会展示在自己的聚集结果中。

7.1 基于兄弟聚集

  • 其于兄弟聚集的管道聚集包括 avg_bucket、max_ bucket、min_bucket、sum_bucket、 stats_bucket、extended_ stats_ bucket、 percentiles_bucket 七种。如 果将它们名称中的 bucket 去除,它们就与本章前面介绍的部分指标聚集同名了。 事实上,它们不仅在名称上接近,而且在功能上也类似,只是聚集运算的范围由整个文档变成了另一个聚集结果。以 avg_bucket 为例, 它的作用是计算兄弟聚集结果中某一指标的平均值:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "carriers": {
            "terms": {
                "field": "Carrier",
                "size": 10
            },
            "aggs": {
                "carrier_stat": {
                    "stats": {
                        "field": "AvgTicketPrice"
                    }
                }
            }
        },
        "all_stat": {
            "avg_bucket": {
                "buckets_path": "carriers>carrier_stat.avg"
            }
        }
    }
}

在这里插入图片描述

  • 例中,最外层包含有两个名称分别为 carriers 和 all_stat 的聚集,这两个聚集就是兄弟关系。
  • carries 聚集是一个针对 Carrier 字段的 terms 聚集,Carrier 字段保存的是航班承运航空公司,所以这个聚集的作用是按航空公司将航班分桶。在这个聚集中 嵌套了一个名为 carrier_stat 的聚集,它是一个针对 AvgTicketPrice 字段的 stats 聚集,会按桶计算票价的最小值、平均值等统计数据。
  • all_ stat 聚集则是一个 avg_bucket 管道聚集,在它的 Path 参数中指定了运 算平均值的路径" carriers>carrier_stat.avg ",即从兄弟聚集中查找 carrier_stat 指标聚集, 然后再用其中的 avg 字段参与平均值计算。 所以 all_ stat 这计算出来的是四个航空公司平均票价的平均值,实际上就是所有航班的平均票价。
  • 尽管示例针对 avg_bucket 管道聚集的检索,但使用其余六种基于兄弟的管道聚集的关键字值替换后,它们就变成了另一种合法的管道聚集了。

7.2 基于父聚集

  • 基于父聚集的管道聚集包括 moving_avg、moving_fn、bucket_script、 bucket_selector、bucket_sort、derivative、cumulative_sum、serial_diff 八种。

7.2.1 滑动窗口

  • moving_avg 和 moving_fn 这两种管道聚集的运算机制相同,都是基于滑动窗口( Siding Window)算法对父聚集的结果做新的聚集运算。滑动窗口算法使用一个具有固定宽度的窗口滑过一组数据, 在滑动的过程中对落在窗口内的数据做运算。moving_avg 管道聚集是对落在窗口内的父聚集结果做平均值运算,而 moving_fn 管道聚集则可以对落在窗口内的父聚集结果做各种自定义的运算。由于 moving_avg 管道可以使用 moving_fn 管道聚集实现,所以 moving_avg 在 Elaticearch 版本 6.4.0 中已经被废止。
  • 由于使用滑动窗口运算时每次移动 1 个位置, 这就要求 moving_avg 和 moving_fn 所在聚集桶与桶间隔必须固定,所以这两种管道聚集只能在 histogam 和 date_histogam 聚集中使用:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "day_price": {
            "date_histogram": {
                "field": "timestamp",
                "interval": "day"
            },
            "aggs": {
                "avg_price": {
                    "avg": {
                        "field": "AvgTicketPrice"
                    }
                },
                "smooth_price": {
                    "moving_fn": {
                        "buckets_path": "avg_price",
                        "window": 10,
                        "script": "MovingFunctions.unweightedAvg(values)"
                    }
                }
            }
        }
    }
}

在这里插入图片描述
在这里插入图片描述

  • 在示例中,最外层的父聚集 day_price 是 1 个 date_histogam 桶型聚集,它根据文档的 timestamp 字段按天将文档分桶。day_price 聚集包含 avg_price 和 smooth_price 两个子聚集,其中 avg_price 聚集是一个求 AvgTicketPrice 字段在 1 个桶内平均值的 avg 聚集,而 smooth_price 则是一个使用滑动窗口做平均值平滑的管道聚集,窗口宽度由参数 window 设置为 10,默认值为 5。
  • 通过返回结果比较 avg_price 与 smooth_price 就会发现,后者由于经过了滑动窗口运算,数据变化要平滑得多。
  • moving _fn 聚集包含一个用于指定运算脚本的 script 参数,在脚本中可以通 过 values 访问 buckets_path 参数指定的指标值。moving _fn 还内置了一个 MovingFunctions 类,包括多个运算函数:
    max()
    min( )
    sum( )
    stdDev( ) 标准偏差
    unweightedAvg( ) 无加权平均值
    linearWeightedAvg( ) 线性加权移动平均值
    ewma( ) 指数加权移动平均值
    holt( ) 二次指数加权移动平均值
    holtWinters( ) 三次指数加权移动平均值

7.2.2 单桶运算

  • 目前我们学习的管道聚集会对父聚集结果中落在窗口内的多个桶做聚集运算,而 bucket_script、 bucket_selector 、bucket_sort 这三个管道聚集则会针对父聚集结果中的每一个桶做单独的运算。其中,bucket_script 会对每个桶执行一段脚本,运算结果会添加到父聚集的结果中, bucket_selector 同样也是执行一 段脚本,但它执行的结果一定是布尔类型,并且决定当前桶是否出现在父聚集的结果中; bucket_sort 则根据每桶中的具体指标值决定桶的次序。下面通过示例来说明这三种管道聚集的具体用法:
POST /kibana_sample_data_flights/_search?filter_path=aggregations
{
    "aggs": {
        "date_price_diff": {
            "date_histogram": {
                "field": "timestamp",
                "fixed_interval": "1d"
            },
            "aggs": {
                "stat_price_day": {
                    "stats": {
                        "field": "AvgTicketPrice"
                    }
                },
                "diff": {
                    "bucket_script": {
                        "buckets_path": {
                            "max_price": "stat_price_day.max",
                            "min_price": "stat_price_day.min"
                        },
                        "script": "params.max_price - params.min_price"
                    }
                },
                "gt990": {
                    "bucket_selector": {
                        "buckets_path": {
                            "max_price": "stat_price_day.max",
                            "min_price": "stat_price_day.min"
                        },
                        "script": "params.max_price - params.min_price > 990"
                    }
                },
                "sort_by": {
                    "bucket_sort": {
                        "sort": [
                            {
                                "diff": {
                                    "order": "desc"
                                }
                            }
                        ]
                    }
                }
            }
        }
    }
}

在这里插入图片描述

  • 在示例中同时应用这三种管道聚集,它们的聚集名称分别为 diff、gt990 和 sort_by。最外层的 date_price_diff 聚集是一个以天为固定间隔的 date_histogram 聚集,其中又嵌套了包括上述三个聚集在内的子聚集。
  • 其中,stat_price_day 是一个根据 AvgTicketPrice 字段生成统计数据的 stats 聚集。diff 是一个 bucket_script 管道聚集,它的作用是向最终聚集结果中添加代表最大值与最小值之差的 diff 字段。它通过 buckets_path 定义了两个参数 max_price 和 min_price, 并在 script 参数中通过脚本计算了这两个值的差作为最终结果,而这个结果将出现在整个聚集结果中。
  • gt990 是一个 bucket_selector 管道聚集,它的作用是筛选哪些桶可以出现在最终的聚集结果中。它也在 buckets_path 中定义了相同的参数,不同的是它的 script 参数运算的不是差值,而是差值是否大于 990,即“ params.max_price - params.min_price > 990 "。如果差值大于 990 即运算结果为 true,那么当前桶将被选取到结果中,否则当前桶将不能在结果中出现。
  • sort_by 是一个 bucket_sort 管道聚集,它的作用是给最终的聚集结果排序。它通过 sort 参数接收一组排序对象, 在示例中是使用 diff 聚集的结果按倒序排序。
  • 所以示例整体的运算效果就是将那些票价最大值与最小值大于 990 的桶选取出来,并在桶中添加 diff 字段保存最大值与最小值的差值,并按 diff 字段值降序排列。

8. 父子关系

  • Elasticsearch 中的父子关系是单个索引内部文档与文档之间的一种关系,父文档与子文档同属一个索引并通过父文档 id 建立联系, 类似于关系型数据库中单表内部行与行之间的自关联,比如有层级关系的部门表中记录之间的关联。

8.1 join 类型

  • 在 Elasticsearch 中并没有外键的概念,文档之间的父子关系通过给索引定义 join 类型字段实现。例如创建一个员工索引 employes, 定义个 join 类型的 managemet 字段用于确定员工之间的管理与被管理关系:
PUT employees
{
    "mappings": {
        "properties": {
            "management": {
                "type": "join",
                "relations": {
                    "manager": "member"
                }
            }
        }
    }
}
  • 在示例中,management 字段的数据类型被定义为 join,同时在该字段的 relations 参数中定义父子关系为 manager 与 member,其中 manager 为父而 member 为子,它们的名称可由用户自定义。文档在父子关系中的地位,是在添加文档时通过 join 类型字段指定的。还是以 employes 索引为例,在向 employees 索引中添加父文档时,应该将 mangement 字段设置为 manager;而添加子文档时则应该设置为 member。具体如下:
PUT /employees/_doc/1
{
    "name": "tom",
    "management": {
        "name": "manager"
    }
}
PUT /employees/_doc/2?routing=1
{
    "name": "smith",
    "management": {
        "name": "member",
        "parent": "1"
    }
}
PUT /employees/_doc/3?routing=1
{
    "name": "john",
    "management": {
        "name": "member",
        "parent": "1"
    }
}
  • 在示例中,编号为 1 的文档其 management 字段通过 name 参数设置为 manager, 即在索引定义父子关系中处于父文档的地位,而编号为 2 和 3 的文档其 management 字段则通过 name 参数设置为 member,并通过 parent 参数指定了它的父文档为编号 1 的文档。
  • 在使用父子关系时,要求父子文档必须要映射到同一分片中,所以在添加子文档时 routing 参数是必须要设置的。显然父子文档在同一分片可以提升在检索时的性能,可在父子关系中使用的查询方法有 has_child、 has_parent 和 parent_id 查询,还有 parent 和 children 两种聚集。

8.2 has_child 查询

  • has_child 查询是根据子文档检索父文档的一种方法,它先根据查询条件将满足条件的子文档检索出来,在最终的结果中会返回具有这些子文档的父文档。例如,如果想检索 smith 的经理是谁,可以:
POST /employees/_search
{
    "query": {
        "has_child": {
            "type": "member",
            "query": {
                "match": {
                    "name": "smith"
                }
            }
        }
    }
}
  • 在示例中,has_child 查询的 type 参数需要设置为父子关系中子文档的名称 member,这样 has_child 查询父子关系时就限定在这种类型中检索; query 参数则 设置了查询子文档的条件,即名称为 smith。最终结果会根据 smith 所在文档, 通过 member 对应的父子关系检索它的父文档。

8.3 has_parent 查询

  • has_parent 查询与 has_child 查询正好相反,是通过父文档检索子文档的一 种方法。 在执行流程上, has_parent 查询先将满足查询条件的父文档检索出来,但在最终返回的结果中展示的是具有这些父文档的子文档。 例如,如果想查看 tom 的所有下属,可以按示例请求:
POST /employees/_search
{
    "query": {
        "has_parent": {
            "parent_type": "manager",
            "query": {
                "match": {
                    "name": "tom"
                }
            }
        }
    }
}

在这里插入图片描述

  • has_parent 查询在结构上与 has_child 查询基本相同,只是在指定父子关系时使用的参数是 parent_type 而不是 type。

8.4 parent_id 查询

  • parent_id 查询与 has_parent 查询的作用相似,都是根据父文档检索子文档。 不同的是,has_parent 可以通过 query 参数设置不同的查询条件;而 parent_id 查 询则只能通过父文档 id 做检索。例如,查询 id 为 1 的子文档:
POST /employees/_search
{
    "query": {
        "parent_id": {
            "type": "member",
            "id": 1
        }
    }
}

在这里插入图片描述

8.5 children 聚集

  • 如果想通过父文档检索与其关联的所有子文档就可以使用 children 聚集。同样以 employess 索引为例,如果想要查看 tom 的所有下属就可以按示例的方式检索:
POST /employees/_search?filter_path=aggregations
{
    "query": {
        "term": {
            "name": "tom"
        }
    },
    "aggs": {
        "members": {
            "children": {
                "type": "member"
            },
            "aggs": {
                "member_name": {
                    "terms": {
                        "field": "name.keyword",
                        "size": 10
                    }
                }
            }
        }
    }
}

在这里插入图片描述

  • 在示例中,query 参数设置了父文档的查询条件,即名称字段 name 为 tom 的文档,而聚集查询 members 中则使用了 children 聚集将它的子文档检索出来, 同时还使用了一个嵌套聚集 member_name 将子文档 name 字段的词项全部展示出来了。

8.6 parent 聚集

  • parent聚集与children聚集正好相反,它是根据子文档查找父文档,parent 聚 集在 Elasrticsearch 版本 6.6 以后才支持。例如通过 name 字段为 smith 的文档, 在找该文档的父文档:
POST /employees/_search?filter_path=aggregations
{
    "query": {
        "match": {
            "name": "smith"
        }
    },
    "aggs": {
        "who_is_manager": {
            "parent": {
                "type": "member"
            },
            "aggs": {
                "manager_name": {
                    "terms": {
                        "field": "name.keyword",
                        "size": 10
                    }
                }
            }
        }
    }
}

在这里插入图片描述

9. 嵌套类型

  • Elastiesearch 的对象类型虽然可按 JSON 对象格式保存结构化的对象数据,但由于 Lucene 并不支持对象类型,所以 Elastiesearch 在存储这种类型的字段时会将它们平铺为单个属性。例如:
PUT colleges/_doc/1
{
    "address": {
        "country": "CN",
        "city": "BJ"
    },
    "age": 10
}
  • address 字段会被平铺为 address.country 和 address.city 两个字段存储。这种平铺存储的方案在存储单个对象时没有什么问题, 但如果在存储数组时会丢失单个对象内部字段的匹配关系。例如
PUT colleges/_doc/2
{
    "address": [
        {
            "country": "CN",
            "city": "BJ"
        },
        {
            "country": "US",
            "city": "NY"
        }
    ],
    "age": 10
}
  • 示例中的 colleges 文档在实际存储时,会被拆解为“ address. country": [“CN”,“US”]” 和 “address. city”:[“BJ” ,“NY”]” 两个数组字段。这样一来,单个对象 内部,country 字段和 city 字段之间的匹配关系就丢失了。换句话说,使用 CN 与 NY 作为共同条件检索的文档时,上述文档也会被检索出来,这在逻辑上就出现了错误:
POST colleges/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "address.country": "CN"
                    }
                },
                {
                    "match": {
                        "address.city": "NY"
                    }
                }
            ]
        }
    }
}
  • 在示例中使用了 bool 组合查询,要求 country 字段为 CN 而 city 字段为 NY。 这样的文档显然并不存在,但由于数组中的对象被平铺为两个独立的数组字段,文档仍然会被检索出来。
    在这里插入图片描述

9.1 nested 类型

  • 为了解决对象类型在数组中丢失内部字段之间匹配关系的问题, Elasticsearch 提供了一种特殊的对象类型 nested。这种类型会为数组中的每一个对象创建一个单独的文档, 以保存对象的字段信息并使它们可检索。由于这类文档并不直接可见,而是藏置在父文档之中,所以这类文档可以称为为隐式文档或嵌入文档。还是以 colleges 索引为例,我们把原有的索引删除,将它的 address 字段设置为 nested 类型:
PUT colleges
{
    "mappings": {
        "properties": {
            "address": {
                "type": "nested"
            },
            "age": {
                "type": "integer"
            }
        }
    }
}
  • 然后重新存入文档 1 和 2,当字段被设置为 nested 类型后,再使用原来查询中的 bool 组合查询就不能检索出来了。这是因为对 nested 类型字段的检索实际上是对隐式文档的检索,在检索时必须要将检索路由到隐式文档上,所以必须使用专门的检索方法。也就是说,现在即使将原来查询中的查询条件设置为 CN 和 BJ 也不会检索出结果。

在这里插入图片描述

  • nested 类型字段可使用的检索方法包括 DSL 的 nested 查询,还有聚集查询中的 nested 和 reverse_nested 两种聚集。

9.2 nested 查询

  • nested 查询只能针对 nested 类型字段,需要通过 path 参数指定 nested 类型 字段的路径,而在 query 参数中则包含了针对隐式文档的具体查询条件。例如:
POST colleges/_search
{
    "query": {
        "nested": {
            "path": "address",
            "query": {
                "bool": {
                    "must": [
                        {
                            "match": {
                                "address.country": "CN"
                            }
                        },
                        {
                            "match": {
                                "address.city": "NY"
                            }
                        }
                    ]
                }
            }
        }
    }
}

在这里插入图片描述

  • 在示例中再次使用 CN 与 NY 共同作为查询条件,但由于使用 nested 类型后 会将数组中的对象转换成隐式文档,所以在 nested 查询中将不会有文档返回了。 将条件更换为 CN 和 BJ,则有文档返回。

在这里插入图片描述

9.3 nested 聚集

  • nested 聚集是一个单桶聚集,也是通过 path 参数指定 nested 字段的路径,包含在 path 指定路径中的隐式文档都将落入桶中。所以 nested 字段保存数组的长度就是单个文档落入桶中的文档数量,而整个文档落入桶中的数量就是所有文档 nested 字段数组长度的总和。有了 nested 聚集,就可以针对 nested 数组中的对象做各种聚集运算,例如:
POST colleges/_search?filter_path=aggregations
{
    "aggs": {
        "nested_address": {
            "nested": {
                "path": "address"
            },
            "aggs": {
                "city_names": {
                    "terms": {
                        "field": "address.city.keyword",
                        "size": 10
                    }
                }
            }
        }
    }
}

在这里插入图片描述

  • 在示例中,nested_address 是一个 nested 聚集的名称,它会将 address 字段的隐式文档归入一个桶中。而嵌套在 nested_address 聚集中的 city_names 聚集则会在这个桶中再做 terms 聚集运算,这样就将对象中 city 字段所有的词项枚举出来了。

9.4 reverse_nested 聚集

  • reverse_nested 聚集用于在隐式文档中对父文档做聚集,所以这种聚集必须作为 nested 聚集的嵌套聚集使用。例如:
POST colleges/_search?filter_path=aggregations
{
    "aggs": {
        "nested address": {
            "nested": {
                "path": "address"
            },
            "aggs": {
                "city names": {
                    "terms": {
                        "field": "address.city.keyword",
                        "size": 10
                    },
                    "aggs": {
                        "avg_age_in_city": {
                            "reverse_nested": {
                                
                            },
                            "aggs": {
                                "avg_age": {
                                    "avg": {
                                        "field": "age"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

在这里插入图片描述

  • 在示例中,city_ names 聚集也是将隐式文档中 city 字段的词项全部聚集出来。不同的是在这个聚集中还嵌套了一个名为 avg_age_in_city 的聚集,这个聚集就是个 reverse_ nested 聚集。它会在隐式文档中将 city 字段具有相同词项的文档归入一个桶中,而 avg_age_in_city 聚集嵌套的另外一个名为 avg_age 的聚集,它会把落入这个桶中文档的 age 字段的平均值计算出来。所以从总体上来看,这个聚集的作用就是将在同一城市中大学的平均校龄计算出来。

10. 使用 SQL 语言

  • 使用 SQL 语言 Elasticsearch 在 Basic 授权中支持以 SQL 语句的形式检索文档,SQL 语句在执行时会被翻译为 DSL 执行。从语法的角度来看,Elastisearch 中的 SQL 语句与 RDBMS 中的 SQL 语句基本一致, 所以对于有数据库编程基础的人来说大大降低了使用 Elaticsearch 的学习成本。
  • Elasticsearch 提供了多种执行SQL 语句的方法,可使用类似_search 样的 REST 接口执行也可以通过命令行执行。它甚至还提供了 JDBC 和 ODBC 驱动来执行 SQL 语句,但 JDBC 和 ODBC 属于 Platinum (白金版)授权需要付费,所以这里只介绍_sql 接口。

10.1 sql 接口

  • 在早期版本中,Elasticsearch 执行 SQL 的 REST 接口为_xpack/sql, 但在版本 7 以后这个接口已经被废止而推荐使用_sql 接口。例如:
POST _sql?format=txt
{
    "query": "select DestCountry, OriginCountry,AvgTicketPrice from kibana_sample_data_flights where Carrier = 'Kibana Airlines' order by AvgTicketPrice desc"
}

在这里插入图片描述

  • 在示例中,_sql接口通过query参数接收SQL语句,而SQL语句也包含有select、 from、where、 orderby 等子句。_sql 接口的 URL 请求参数 format 定义了返回结果格式。比如在示例中定义了返回结果格式为 txt。除了 txt 以外,_sqI 接口还支 持 csv、 json、tsv、yaml 等等格式。
  • 示例中的请求会将所有航空公同为 Kibana Airines 的航班文档检索出来,并以文本表格的形式返回
  • 对于总量比较大的 SOL 查询,sql 接口还支持以游标的形式实现分页。当_sql 接口的请求参数中添加了 fetch_size 参数,sql 接口在返回结来时就会根据 fetch_size 参数设置的大小返回相应的条数,并在返回结果中添加游标标识。具体来说,当请求_sql 接口时设置的 forma 为 json 时,返回结果中会包含 cursor 属性;而其他情况下则会在响应中添加Cursor报头。例如还是执行示例中的SOL , 但是加入分页支持:
POST _sql?format=json
{
    "query": "select DestCountry, OriginCountry,AvgTicketPrice from kibana_sample_data_flights where Carrier = 'Kibana Airlines' order by AvgTicketPrice desc",
    "fetch_size": 10
}

在这里插入图片描述

  • 在示例的请求中,为了能够在返回结果中直接看到 cursor 值,我们将 format 设置为 json,可以看到:

在这里插入图片描述

POST _sql?format=json
{
     "cursor" : "g+azAwFaAXN4RkdsdVkyeDFaR1ZmWTI5dWRHVjRkRjkxZFdsa0RYRjFaWEo1UVc1a1JtVjBZMmdCRmpZNFFVbzBUbU5wVXpReVpsWmhkVnBaZFdkbVlWRUFBQUFBQUFDend4WTJXSGd0UTI5S2FsTnhkV281YTI0M01UWmFhbTVS/w8DAWYLRGVzdENvdW50cnkBB2tleXdvcmQAAAFmDU9yaWdpbkNvdW50cnkBB2tleXdvcmQAAAFmDkF2Z1RpY2tldFByaWNlAQVmbG9hdAAAAQc="
}

在这里插入图片描述

  • 而在上面的请求中,参数 cursor 就是第一个请求返回结果中的 cursor 值, 反复执行请求,Elasticsearch 就会将第一次请求的全部内容以每次 10 个的数量全部迭代出来。在请求完所有数据后,应该使用_sql/close 接口将游标关闭以释放资源。
POST _sql/close
{
     "cursor" : "g+azAwFaAXN4RkdsdVkyeDFaR1ZmWTI5dWRHVjRkRjkxZFdsa0RYRjFaWEo1UVc1a1JtVjBZMmdCRmpZNFFVbzBUbU5wVXpReVpsWmhkVnBaZFdkbVlWRUFBQUFBQUFDend4WTJXSGd0UTI5S2FsTnhkV281YTI0M01UWmFhbTVS/w8DAWYLRGVzdENvdW50cnkBB2tleXdvcmQAAAFmDU9yaWdpbkNvdW50cnkBB2tleXdvcmQAAAFmDkF2Z1RpY2tldFByaWNlAQVmbG9hdAAAAQc="
}

在这里插入图片描述

  • 除了 fetch_size 以外还有些可以在_sql 接口请求体中使用的参数,如下:
    query 需要执行的 SQL 语句,必须要设置的参数
    fetch_size 默认 1000,每次返回的行数
    filter 默认 none,使用 DSL 设置过滤器
    request_timeout 默认 90s,请求超时时间
    page_timeout 默认 45s,分页超时时间
    tume_zone 默认 Z,时区
    field_multi_value_leniency 默认 false,如果一个字段返回多个值时是否忽略
  • 在这些参数中,fiter 可以使用 DSL 对文档做过滤,支持 DSL 中介绍的所有查询条件。query 中的 SQL 语句在翻译为 DSL 后,会与 filter 中的 DSL 查询语句共 同组合到 bool 查询中。其中 SQL 语句生成的 DSL 将出现在 must 子句,而 filter 中的 DSL 则出现在 filter 子句中。来想要查看 SQL 语句翻译后的 DSL. 可以使用 _sql/translate 执行相同的请求,在返回结果中就可以看到翻译后的 DSL 了。

10.2 SQL 语法

  • Elasticsearch 支持传统关系型数据库 SQL 语句中的查询语句,但并不支持 DML、DCL 句。换句话说,它只支持 SELECT 语句,不支持 INSERT、UPDATE、DELETE 语句。SELECT 语句以外,Elaticsarch 还支持 DESCRIBE 和 SHOW 语句。

10.2.1 SELECT 语句

  • SELECT 语句用于查询文档,基本语法格式
    SELECT select _expr,
    [ FROM table_name]
    [ WHERE condtion]
    [ GROUP BY grouping_element]
    [ HAVING condition]
    [ ORDER BY expression ASC|DESC]
    [ LIMIT count]
  • Elastiesearch 的 SELECT 语句跟普通 SQL 几乎没有什么 区别,支持 SELECT、FROM、 WHERE、 GROUP BY、HAVING、 ORDER BY 及 LIMTT 子句。
  • SELECT 子句中可以使用星号或文档字段名称列表,FROM 子句则指定要检索的索引名称,而 WHERE 子句则设定了检索的条件。一般的 SQL 查询使用这三个子句就足够了,而 GROUP BY 和 HAVING 子句则用于分组,ORDER BY 子句用于排序,而 LIMIT 一般则可以用于分页。和传统 SQL 语句非常接近。

10.2.2 DESCRIBE 语句

  • DESCRIBE 语句用于查看一个索引的基础信息,在返回结果中一般会包含 column、type、mapping 三个列,分别对应文档的字段名称、传统数据库类型及文档字段中的类型。例如要查看索引的基本信息:
POST _sql?format=txt
{
    "query": "describe kibana_sample_data_flights"
}

在这里插入图片描述

10.2.3 SHOW 语句

  • SHOW 语句包括三种形式,即 SHOW COLUMNS、SHOW FUNCTIONS 和 SHOW TABLES 。
  • SHOW COLUMNS 用于查看一个索引中的字段情况,它的作用与 DESCRIBE 语句完全一样,其至连返回结果都是一样的
  • SHOW FUNCTIONS 用于返回在 Elastiesearch SQL 中支持的所有函数,返回结 果中包括 MIN、MAX、COUNT 等常用的聚集函数。
  • SHOW TABLES 用看 Elaticecearch 中所有的索引。
POST _sql?format=txt
{
    "query": "show columns in kibana_sample_data_flights"
}

在这里插入图片描述

POST _sql?format=txt
{
    "query":"show functions"
}

在这里插入图片描述

POST _sql?format=txt
{
    "query":"show tables"
}

在这里插入图片描述

  • 这三种形式都支持使用 LIKE 子句过滤返回结果,LIKE 子句在用法上与 SQL 语句中的 LIKE 类似。例如,“show functions like 'a%"将只返回以 a 开头的函数。

10.2.4 操作符与函数

  • Elasticsearch SQL 中支持的操作符与函数有 100 多种,这些操作符大多与普通 SQL 语言一致,所以这里只介绍一些与普通 SQL 语句不一样的地方。
  • 先来看一下比较操作符。一般等于比较在 SQL 中使用等号“=”,这在 ElasticsearchSQL 中也成立。但是 Elasticseareh SQL 还引人了另一个等号比较“< = >”,这种等号可以在左值为 null 时不出现异常。
POST _sql?format=txt
{
    "query":"select null = 'elastic'"
}

在这里插入图片描述

POST _sql?format=txt
{
    "query":"select null <=> 'elastic'"
}

在这里插入图片描述

  • LIKE 操作符,在 LIKE 子句中可以使用%代表任意多个字符,而使用下划线 _ 代表单个字符。Elasticsearch SQL 不仅支持 LIKE 子句,还支持通过 RLIKE 子句以正则表达式的形式做匹配,这大大扩展了 SQL 语句模糊匹配的能力。
  • 尽管使用 LIKE 和 RLIKE 可以实现模糊匹配,但它离全文检索还差得很远。SQL 语句的 WHERE 子句一般都是使用字段整体值做比较,而没有使用词项做匹配的能力。为此 Elasticsearch SQL 提供了 MATCH 和 OUERY 两个函数,以实现在 SQL 做全文检索。例如使用 match 和 query 函数,它们的作用都是检索 DestCounty 字段为 CN 的文档:
POST _sql?format=txt
{
    "query": "select DestCountry, OriginCountry,AvgTicketPrice,score() from kibana_sample_data_flights where match(DestCountry,'CN')"
}

在这里插入图片描述

POST _sql?format=txt
{
    "query": "select DestCountry, OriginCountry,AvgTicketPrice,score() from kibana_sample_data_flights where query('DestCountry:CN')"
}

在这里插入图片描述

  • 在示例中的两个请求的 selet 子句中都使用了 SCORE 函数,它的作用是获取检索的相关度评分值。
  • Elasticsearch SQL支持传统SQL中的聚集函数,这包括MAX、MIN、AVG、COUNT、 SUM 等。同时,它还支持一些 Elasticsearch 特有的聚集函数,这些聚集函数与 Elasticsearch 聚集查询相对应。这包括 FIRST/FIRST_ VALUE 和 LAST/LAST VALUE, 可用于查看某个字段首个和最后一个非空值; PERCENTILE 和 PERCENTILE RANK 用 于百分位聚集,KURTOSIS,SKEWNESS、STDDEV_ POP、SUM_OF SQUARES 和 VAR_ POP 可用于运算其他统计聚集。除了以上这些函数和操作符,Elasticsearch SQL 还定义了一组用于日期、数值以及字符串运算的函数。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值