编程随笔-ElasticSearch知识导图(5):聚合

作者: 简单是美美 | 来源:发表于2019-02-28 10:47 被阅读8次

    1. 聚合模式

      聚合(Aggregations)是对数据库中数据域进行统计分析的手段,关系数据库中我们常会用到avg,sum,count,group by这些聚合手段进行简单的统计与分析。在ES中也提供了同样的功能,根据使用模式,分为以下几种:

    • 数字指标(metrics)聚合:根据输出的是单值的还是多值的分为单值数字指标与多值数字指标,计算使用的域可直接从文本中抽取也可使用脚本生成。
    • 分组(bucket)聚合:分组聚合创建文档对象的分组。每个分组都与一个分组依据 (凭证)相关联(取决于聚合类型),该依据确定当前上下文中的文档是否“属于”其中。分组聚合还计算并返回每个分组中文档数量。分组聚合可以嵌套,即一个分组中还可以定义子分组。分组聚合支持对父子关系对象和嵌套对象的聚合。
    • 管道(Pipeline)聚合:处理来自其它聚合的数据,而不是直接计算文档对象的域值得到输出。管道聚合可以分为两类:
      • 父(parent)聚合:一组管道聚合的输入数据由其父聚合的输出提供,能够计算新分组或新聚合添加到现有组中。
      • 兄弟(sibling)聚合:输入数据由同级聚合的输出提供,新产生的聚合域与所使用的输入聚合同级。

      文献1中还提到了矩阵(Matrix)聚合,它对多个字段进行操作,并根据字段值生成一个矩阵结果,该矩阵是对这些字段的一些统计数据。因为比较小众,本文中不做讨论。
      数字指标聚合、分组聚合类似于关系数据库中的avg,sum,count,group by等聚合形式,在应用系统中经常会使用。管道聚合是数字指标聚合及分组聚合的进阶使用,语法派生于数字指标聚合、分组聚合,本文暂不探讨,有兴趣的同学看参考文献1。
      可将数字指标聚合、分组聚合的语法和用法总结如下一张导图。


    聚合语法与用法

    2. 与查询指令结合

      聚合指令使用检索DSL(search DSL)定义,因而也使用检索指令的URI(标识为“_search”),请求消息体中若包含以“query”指示的查询指令,则以“aggs”指示的聚合指令进行聚合操作的对象为“query”指令的查询结果;若不包含“query”指令,则表示进行聚合操作的对象为索引中所有对象。
      仍以《编程随笔-ElasticSearch知识导图(3):映射》中第2节中的银行账号索引为例,考察下面一个简单聚合指令,计算银行余额的均值:

    curl -iXPOST 'localhost:9200/bank/_search?pretty'  -H 'Content-Type: application/json' -d'
    {
        "size":0,
        "aggs": {
            "avg_balance": {
                "avg": {
                    "field": "balance"
                }
            }
        }
    }
    '
    

      该命令计算bank索引中所有账户的余额平均值,若想查询年龄在30到40之间客户的记录和平均余额,则可使用下面的指令。

    curl -iXPOST 'localhost:9200/bank/_search?pretty'  -H 'Content-Type: application/json' -d'
    {
        "query": {
            "range": {
                "age": {
                    "lte": 40,
                    "gte": 30
                }
            }
        },
        "aggs": {
            "avg_balance": {
                "avg": {
                    "field": "balance"
                }
            }
        }
    }
    '
    

      若只是想了解年龄在30到40之间客户的平均余额,则可使用如下聚合指令(注意范围分组中不包含“to”的值):

    curl -iXPOST 'localhost:9200/bank/_search?pretty'  -H 'Content-Type: application/json' -d'
    {
        "size":0,
        "aggs": {
            "avg_balance_by_age": {
                "range": {
                    "field": "age",
                    "ranges": [
                        {
                            "to": 41,
                            "from": 30
                        }
                    ]
                },
                "aggs": {
                    "avg_balance": {
                        "avg": {
                            "field": "balance"
                        }
                    }
                }
            }
        }
    }
    '
    

    3. 常用模式设计

    3.1. 聚合模式表示

      以我们熟悉的SQL语言作为范式,我们将应用中的常用聚合查询使用SQL表示为如下模式:

    SELECT [$field_1] FROM $index_name WHERE $filter_clause GROUP BY [$field_2] ORDER BY [$field_3]
    

      其中:

    • [$field_1]是在返回结果显示的字段名集合,$field_1有可能是实施聚合操作的聚合值,也可以是分组[$field_2]中的字段。
    • $index_name是索引名。
    • [$field_2]是分组依据的字段,可能为多个字段。
    • [$field_3]是排序字段,可能为多个字段。
    • $filter_clause是过滤条件。

    3.2. 多分组字段

      对于聚合中的多个分组字段,在聚合指令中可以使用两种格式:一种使用 基于“terms”子句的嵌套分组方式,另一种使用基于“composite”子句的多字段分组方式。
      本文建议如果有只有一个分组字段,使用”terms”定义分组,如果包含多个分组字段,则使用“composite”定义多个分组字段。
      考虑如下聚合查询用例,按账户所在的州与性别分组,获取每组的余额最大值:

    SELECT state,gender,max(balance) FROM bank GROUP BY state,gender 
    

      使用基于“composite”子句的分组方式聚合指令如下所示:

    curl -iXPOST 'localhost:9200/bank/_search?pretty'  -H 'Content-Type: application/json' -d'
    {
        "size": 0,
        "aggs": {
            "group_by_state_gender": {
                "composite": {
                    "sources": [
                        {
                            "state": {
                                "terms": {
                                    "field": "state.keyword"
                                }
                            }
                        },
                        {
                            "gender": {
                                "terms": {
                                    "field": "gender.keyword"
                                }
                            }
                        }
                    ]
                },
                "aggs": {
                    "max_balance": {
                        "max": {
                            "field": "balance"
                        }
                    }
                }
            }
        }
    }
    '
    

      返回结果(部分)显示如下:

    "aggregations" : {
        "group_by_state_gender" : {
          "after_key" : {
            "state" : "AK",
            "gender" : "F"
          },
          "buckets" : [
            {
              "key" : {
                "state" : "AK",
                "gender" : "F"
              },
              "doc_count" : 10,
              "max_balance" : {
                "value" : 44043.0
              }
            }
          ]
        }
      }
    

      使用基于“terms”子句的嵌套分组方式聚合指令如下所示:

    curl -iXPOST 'localhost:9200/bank/_search?pretty'  -H 'Content-Type: application/json' -d'
    {
        "size": 0,
        "aggs": {
            "group_by_state": {
                "terms": {
                    "field": "state.keyword"
                },
                "aggs": {
                    "group_by_gender": {
                        "terms": {
                            "field": "gender.keyword"
                        },
                        "aggs": {
                            "max_balance": {
                                "max": {
                                    "field": "balance"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    '
    

      返回结果(部分)显示如下所示:

    "aggregations" : {
        "group_by_state" : {
          "doc_count_error_upper_bound" : 28,
          "sum_other_doc_count" : 978,
          "buckets" : [
            {
              "key" : "TX",
              "doc_count" : 22,
              "group_by_gender" : {
                "doc_count_error_upper_bound" : 0,
                "sum_other_doc_count" : 0,
                "buckets" : [
                  {
                    "key" : "F",
                    "doc_count" : 13,
                    "max_balance" : {
                      "value" : 49587.0
                    }
                  },
                  {
                    "key" : "M",
                    "doc_count" : 9,
                    "max_balance" : {
                      "value" : 42736.0
                    }
                  }
                ]
              }
            }
          ]
        }
      }
    

      从两种查询方式的结果格式来看,使用“composite”方式的查询指令返回结果更符合我的使用习惯。

    3.3. 排序

      可对聚合查询的结果用于拍寻,用于排序字段的可为分组字段,也可为聚合操作结果。将上节的查询要求改为如下形式:

    SELECT state,gender,max(balance) FROM bank GROUP BY state,gender ORDER BY state ASC ,gender ASC
    

      则查询指令可修改为如下形式:

    curl -iXPOST 'localhost:9200/bank/_search?pretty'  -H 'Content-Type: application/json' -d'
    {
        "size": 0,
        "aggs": {
            "group_by_state_gender": {
                "composite": {
                    "sources": [
                        {
                            "state": {
                                "terms": {
                                    "field": "state.keyword",
                                    "order": "ASC"
                                }
                            }
                        },
                        {
                            "gender": {
                                "terms": {
                                    "field": "gender.keyword",
                                    "order": "ASC"
                                }
                            }
                        }
                    ]
                },
                "aggs": {
                    "max_balance": {
                        "max": {
                            "field": "balance"
                        }
                    }
                }
            }
        }
    }
    '
    

      需要注意的是:“composite”形式的聚合查询只支持对分组字段的排序,如果要使用聚合值作为排序字段,请使用“terms”形式用于分组的子句,如下面的示例。

    curl -iXPOST 'localhost:9200/bank/_search?pretty'  -H 'Content-Type: application/json' -d'
    {
        "size": 0,
        "aggs": {
            "group_by_state": {
                "terms": {
                    "field": "state.keyword",
                    "order": {
                        "max_balance": "DESC"
                    }
                },
                "aggs": {
                    "max_balance": {
                        "max": {
                            "field": "balance"
                        }
                    }
                }
            }
        }
    }
    '
    

    3.4. 分页

      如果聚合查询的返回记录较多,ES在一次返回结果中默认返回10条。如果需要获取所有记录,则需要设置分页参数进行多次查询。
      仍然考虑3.2节的查询示例,分组结果可能有100个左右的分组,若设置每次查询结果返回5个分组,可以设置如下查询指令:

    curl -iXPOST 'localhost:9200/bank/_search?pretty'  -H 'Content-Type: application/json' -d'
    {
        "size": 0,
        "aggs": {
            "group_by_state_gender": {
                "composite": {
                    "size": 5,
                    "sources": [
                        {
                            "state": {
                                "terms": {
                                    "field": "state.keyword",
                                    "order": "ASC"
                                }
                            }
                        },
                        {
                            "gender": {
                                "terms": {
                                    "field": "gender.keyword",
                                    "order": "ASC"
                                }
                            }
                        }
                    ]
                },
                "aggs": {
                    "max_balance": {
                        "max": {
                            "field": "balance"
                        }
                    }
                }
            }
        }
    }
    '
    

      对于使用了“composite”形式的查询指令,在返回结果中包含一个“after_key”对象,标识本次查询结果的最后一个分组标识,如果在下次查询中携带该对象,ES会返回此对象所标识分组后面的分组记录,查询指令如下所示(注意指令中的“after”对象,提供了类似游标的功能,每次根据上次查询结果的“after_key”进行改变):

    curl -iXPOST 'localhost:9200/bank/_search?pretty'  -H 'Content-Type: application/json' -d'
    {
        "size": 0,
        "aggs": {
            "group_by_state_gender": {
                "composite": {
                    "size": 5,
                    "after": {
                        "state" : "AR",
                        "gender" : "F"
                    },
                    "sources": [
                        {
                            "state": {
                                "terms": {
                                    "field": "state.keyword",
                                    "order": "ASC"
                                }
                            }
                        },
                        {
                            "gender": {
                                "terms": {
                                    "field": "gender.keyword",
                                    "order": "ASC"
                                }
                            }
                        }
                    ]
                },
                "aggs": {
                    "max_balance": {
                        "max": {
                            "field": "balance"
                        }
                    }
                }
            }
        }
    }
    '
    

      对于使用 “terms”的嵌套分组方式的聚合查询指令无法使用类似“游标”功能,只能返回指定数目的分组结果。

    3.5. 过滤条件处理

      如果聚合查询中有过滤条件,最简单的方式是在查询指令中增加“query”子句,参看第2节的描述。

    3.6. 设计模式

      现在我们可以对查询要求:

    SELECT [$field_1] FROM $index_name WHERE $filter_clause GROUP BY [$field_2] ORDER BY [$field_3]
    

      定义一个常用的聚合查询模式,如下所示:

    {
        "query": {
            "$filter_clause": {}
        },
        "aggs": {
            "group_by_field": {
                "composite": {
                    "size": {},
                    "after": {},
                    "sources": [
                        "[$field_2]",
                        "[$field_3]"
                    ]
                },
                "aggs": {
                    "aggregate_operation": {
                        "[$field_1]": {}
                    }
                }
            }
        }
    }
    

      考虑如下查询要求:

    SELECT state,gender,max(balance)  FROM bank WHERE age>=40 GROUP BY state,gender ORDER BY state ASC ,gender ASC 
    

      使用上面的设计模式,可以表示为如下指令:

    curl -iXPOST 'localhost:9200/bank/_search?pretty'  -H 'Content-Type: application/json' -d'
    {
       "size": 0,
       "query": {
           "range": {
               "age": {
                   "gte": 40
               }
           }
       },
       "aggs": {
           "group_by_state_gender": {
               "composite": {
                   "size": 5,
                   "sources": [
                       {
                           "state": {
                               "terms": {
                                   "field": "state.keyword",
                                   "order": "ASC"
                               }
                           }
                       },
                       {
                           "gender": {
                               "terms": {
                                   "field": "gender.keyword",
                                   "order": "ASC"
                               }
                           }
                       }
                   ]
               },
               "aggs": {
                   "max_balance": {
                       "max": {
                           "field": "balance"
                       }
                   }
               }
           }
       }
    }
    '
    

    4. SQL访问支持

      最后告诉大家一个好消息,ES提供SQL语言访问,基于XPACK插件实现。相比于复杂的检索DSL,SQL对于习惯于关系数据库的用户更加亲切一些。
      上节的查询要求可表示为如下SQL访问指令:

    curl -iXPOST 'localhost:9200/_xpack/sql?format=txt'  -H 'Content-Type: application/json'  -d'
    {
        "query": "SELECT state,gender,max(balance) FROM bank WHERE age>=40 GROUP BY state,gender ORDER BY state ASC ,gender ASC"
    }
    '
    

      查询结果如下所示:

    HTTP/1.1 200 OK
    Cursor: w6XxAgFmAWMBBGJhbmu+AQEBCWNvbXBvc2l0ZQdncm91cGJ5AQNtYXgEMTk5MQAA/wEHYmFsYW5jZQAAAP8AAP8CAAQxOTg3AQ1zdGF0ZS5rZXl3b3JkAAAB/wAAAAQxOTgzAQ5nZW5kZXIua2V5d29yZAAAAf8AAOgHAQoCBDE5ODcAAldZBDE5ODMAAU0AAgEAAAAAAQD/////DwAAAAABBXJhbmdlP4AAAAADYWdlAQAAACj/AQAAAAAAAAAAAAAAAVoDAAIAAAAAAAHZ////DwMBawQxOTg3AAABawQxOTgzAAABbQQxOTkxBXZhbHVlAAMAAAAPAAAADwAAAA8=
    Took-nanos: 12179132
    content-type: text/plain
    content-length: 1920
    
         state     |    gender     | MAX(balance)  
    ---------------+---------------+---------------
    AK             |F              |44043.0        
    AK             |M              |37074.0        
    AL             |M              |34743.0        
    CA             |M              |25892.0        
    DC             |F              |18956.0        
    HI             |M              |2171.0         
    ID             |F              |19955.0        
    ID             |M              |16163.0        
    IL             |M              |23165.0        
    IN             |M              |11298.0        
    KY             |F              |48972.0        
    KY             |M              |47887.0        
    MA             |F              |35247.0        
    MI             |F              |13109.0        
    MN             |F              |5346.0         
    MO             |F              |49671.0        
    MO             |M              |31865.0        
    MS             |M              |29316.0        
    MT             |F              |37720.0        
    NC             |M              |34754.0        
    ND             |F              |28969.0        
    ND             |M              |46568.0        
    NH             |F              |19630.0        
    NH             |M              |2905.0         
    NM             |F              |13478.0        
    NM             |M              |44235.0        
    OH             |F              |42072.0        
    OK             |F              |28729.0        
    OR             |M              |33882.0        
    PA             |F              |49159.0        
    SC             |M              |29648.0        
    TX             |M              |6507.0         
    UT             |F              |35896.0        
    UT             |M              |43532.0        
    VT             |F              |9597.0         
    WA             |M              |18400.0        
    WV             |F              |16869.0        
    WY             |M              |32849.0    
    

      ES提供的SQL访问有一些限制:如结果的返回字段要么是分组字段,要么是聚合值;排序字段不可为聚合值等。检索DSL语法复杂,但功能更加强大。若要快速开发,ES提供的SQL访问也不失为一种选择。

    5. 参考文献

    1. https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
    2. Clinton Gormley &Zachary Tong, Elasticsearch: The Definitive Guide,2015

    相关文章

      网友评论

        本文标题:编程随笔-ElasticSearch知识导图(5):聚合

        本文链接:https://www.haomeiwen.com/subject/feocuqtx.html