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. 參考文獻
- https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
- Clinton Gormley &Zachary Tong, Elasticsearch: The Definitive Guide,2015
本系列文章:
編程隨筆-ElasticSearch知識導圖(1):全景
編程隨筆-ElasticSearch知識導圖(2):分佈式架構
編程隨筆-ElasticSearch知識導圖(3):映射
編程隨筆-ElasticSearch知識導圖(4):搜索
編程隨筆-ElasticSearch知識導圖(5):聚合
編程隨筆-ElasticSearch知識導圖(6):管理