前言 這篇文章是出自於線上課程 Complete Guide to Elasticsearch 的所記錄的筆記。
這篇文章使用的 ES 版本為 7.16.2
這一篇文章要來介紹 Elastic 的 aggragation 如何使用。
正文 什麼是 aggregation? Aggregation 代表的是從一群資料中彙整成指標、數據或是可分析的資訊。
Aggregation 又能分成 single value aggregation
& multi value aggreation
。
aggregation 基本的格式
1 2 3 4 5 6 7 8 9 GET /_search { "size": 0, "aggs": { "<NAME>": { "<AGG_TYPE>": {} } } }
有哪些 aggregation? sum, avg, max, min 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # 計算 total_amount 的 sum, avg, max, min # 並賦予它他們新的欄位 `total_sales` `avg_sales`, `max_sales`, `min_sales` GET /_search { "size": 0, "aggs": { "total_sales": { "sum": { "field": "total_amount" } }, "avg_sales": { "avg": { "field": "total_amount" } }, "max_sales": { "avg": { "field": "total_amount" } }, "min_sales": { "avg": { "field": "total_amount" } } } }
cadinality cadinality 用來計算有幾個 獨立 的值(近似值)。
1 2 3 4 5 6 7 8 9 10 { "size": 0, "aggs": { "total_salemen": { "cardinality": { "field": "salesman.id" } } } }
count count 用來計算有幾個值。
1 2 3 4 5 6 7 8 9 10 { "size": 0, "aggs": { "values_count": { "value_count": { "field": "total_amount" } } } }
stats 顯示該欄位相對應的 count
& min
& max
& avg
& sum
1 2 3 4 5 6 7 8 9 10 { "size": 0, "aggs": { "amount_stats": { "stats": { "field": "total_amount" } } } }
bucket aggregation 將資料分類成不同的 bucket。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # order - 排序 # missing - 當欄位不存在時,要用什麼名稱取代 # min_doc_count - 符合條件的最小數量要求,預設是 `1` { "size": 0, "aggs": { "status_terms": { "terms": { "field": "status.keyword", "missing": "N/A", "min_doc_count": 0 "order": { "_key": "asc" } } } } }
bucket aggergation 的 response 預設是 10 筆,超過的數量會以 sum_other_doc_count
表示。 doc_count_error_upper_bound 代表的就是未被撈取到的資料總和,意味著未被考慮到的資料最大值。
Document count Term aggregation 找到的資料不一定是精準的,因為同一種資料被分在不同的 shard 當中,若是今天撈取的資料 size=3
,欲撈取的資料在該 shard 中並不是前三名的,那麼該值就不會被納入計算。
舉例來說,假如現在要撈的資料是商品 A, B, F。從下圖可知,只有商品 A 是前三名,商品 B & F 在 shard B & C 中,並不都是前三名。因此,只有 shard A, B 的 商品 B 被計算; 同理可知,只有 shard A, C 的 商品 F 被計算,也就導致了實際撈到的值跟正確的值有差距的結果。
Nested Aggregation 在一個 agg
裡面,再包一層 agg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 先將資料透過 status 分成不同的 buckets 後,再將每一組 bucket 內的資料欄位 `total_amount` 進行 stats 運算 GET /_search { "size": 0, "aggs": { "status_terms": { "terms": { "field": "status" }, "aggs": { "status_stats": { "stats": { "field": "total_amount" } } } } } }
過濾 - filter aggregation 先將符合條件的資料過濾出來再進行 aggreation。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 將資料進行 filter range 的方式讓資料變少,再計算 avg GET /_search { "size": 0, "aggs": { "low_value": { "filter": { "range": { "total_amount": { "lt": 50 } } }, "aggs": { "avg_amount": { "avg": { "filed": "total_amount" } } } } } }
filters - 自定義 filter 透過 filters
來定義規則。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 # 自定義一個 filter `my_filter`,並尋找 title 有 `pasta` & `speaghetti` 的資料 GET /_search { "size": 0, "aggs": { "my_filter": { "filters": { "filters": { "pasta": { "match": { "title": "pasta" } }, "spaghetti": { "match": { "title": "speaghetti" } } } }, "aggs": { "avg_rating": { "avg": { "field": "ratings" } } } } } }
Ranges aggregation 透過 range
的方式來達成 aggregation
range
aggregation1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 GET /order/_search { "size": 0, "aggs": { "amount_distribution": { "range": { "field": "total_amount", "ranges": [ { "to": 50 }, { "from": 50, "to": 100 }, { "from": 100 } ] } } } }
鍵值 to
- 不包含,鍵值 from
- 包含
date_range
aggregation1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # 透過 format 來指定日期格式 # 透過 key 來指定輸出欄位名稱 GET /order/_search { "size": 0, "aggs": { "purchased_ranges": { "date_range": { "format": ""yyyy-MM-dd", "field": "purchased_at", "ranges": [ { "from": "2016-01-01", "to": "2016-01-01||+6M" "key": "first_half" }, { "from": "2016-01-01||+6M", "to": "2016-01-01||+1y", "key": "second_half" } ] } } } }
預設回傳的日期格式不適合閱讀,透過修改 format
的方式,讓回傳的資料更有易讀性,但通常不推薦這麼做,而是把處理格式化的問題交給開發人員
長條圖 Histogram 可以透過 histogram
將資料以長條圖 的方式呈現
資料是以 round down 的方式歸類。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 將欄位 total_amount 以區間 25 來分佈 # min_doc_count 用來排除資料為空的狀況 # extended_bounds 用來強制設定長條圖區間的上下界 GET /order/_search { "size": 0, "aggs": { "amount_distribution": { "histogram": { "field": "total_amount", "interval": 25 } } } }
如果是日期資料,則使用 calendar_interval
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 可以透過 offset 的方式修改時間 GET /order/_search { "size": 0, "aggs": { "orders_over_time": { "calendar_interval": { "field": "purchased_at", "calendar_interval": "month", "offset": "+6h" } } } }
global 透過 global
,會無視 query 的內容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # stats_expensive 與 global 拿到的資料數量不同,因為 `all_orders` 才有 global GET /order/_search { "query": { "range": { "total_amount": { "gte": 100 } } }, "size": 0, "aggs": { "all_orders": { "global": { }, "aggs": { "stats_amount": { "stats": { "field": "total_amount" } } } }, "stats_expensive": { "stats": { "field": "total_amount" } } } }
missing 透過 missing
可以讓我們找到該欄位不存在或 是Null 的資料筆數。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # 假設有兩筆資料如下 POST /order/_doc/1001 { "total_amount": 100 } POST /order/_doc/1002 { "total_amount": 200, "status": null } # 搜尋 missing 的筆數 # 在沒有 status 的資料中, 計算 total_amount 的加總 GET /order/_doc/_search { "size": 0, "aggs": { "orders_without_status": { "missing": { "field": "status.keyword" }, "aggs": { "missing_sum": { "sum": { "field": "total_amount" } } } } } }
Aggregated nested objects 若資料是 objects list,需要使用 nested
來取得資料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # 假設資料長這樣 { "employees":[ { "age": 24, "name": "Percy" }, { "age": 30, "name": "James" } ] } # 取得 employees object 中最小年紀的 GET /department/_search { "size": 0, "aggs": { "employees": { "nested": { "path": "employees" }, "aggs": { "minimum_age": { "min": { "field": "employees.age" } } } } } }
Reference
Complete Guide to Elasticsearch