編程隨筆-ElasticSearch知識導圖(5):聚合 1. 聚合模式 2. 與查詢指令結合 3. 常用模式設計 4. SQL訪問支持 5. 參考文獻 本系列文章:

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知識導圖(1):全景
編程隨筆-ElasticSearch知識導圖(2):分佈式架構
編程隨筆-ElasticSearch知識導圖(3):映射
編程隨筆-ElasticSearch知識導圖(4):搜索
編程隨筆-ElasticSearch知識導圖(5):聚合
編程隨筆-ElasticSearch知識導圖(6):管理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章