Elasticsearch 聚合基礎(一): 分组聚合(bucketing)

Elasticsearch 聚合搜尋: 分組

Elasticsearch 的聚合搜尋, 可以說是最常的用的功能了. 什麼是聚合搜尋呢? 就是針對搜尋出來的結果, 再去做計算. 比如可以計算最大值、平均值、最小值、總和、95%、分組、累加... 等等的計算.

這篇從基礎的分組開始帶大家了解如何做聚合搜尋, 以及他的概念.


範例: 球球分類

首先, 我們有一堆球球; 這些球球都有自己個別的元素, 包含形狀和顏色. 所謂分類就是讓有相同屬性的球球分到一類, 比如相同的形狀或是相同的顏色.

透過分類, 我們可以得到

  1. 這一坨球球裡面有哪些形狀?
  2. 每個形狀有幾顆球球?
  3. 每個形狀, 有幾個不同顏色的球球?

建立資料

那就先把資料建立進去吧, 還沒安裝的朋友請參考Hello World 系列 - Elasticsearch.

我們用批量上傳的方式來丟資料, 可以在這邊下載原始資料balls.json
注意最後一行要空白!

然後將他上傳: curl -H "Content-Type: Application/json" -XPOST 192.168.40.41:9200/demo/doc/_bulk --data-binary @balls.json

大家記得把 elasticsearch ip 換成自己的~


然後看一下, 25顆球都歸檔啦~

❯ curl http://192.168.40.41:9200/_cat/indices\?v
health status index               uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   demo                bBTVU1ssRjKvoIamaO7Irg   3   1         25            0       34kb         20.7kb

來分類吧: 用形狀分類

第一步, 我們先用形狀來分類. 我們預期得到以下結果:


注意圖片中每個分組寫著Buckets, 意思就是; Elasticsearch 當中的分組都適用桶裝的唷.

所以怎麼用 elasticsearch 辦到呢? 咱們接著做囉!

以詞(term)分類來作聚合搜尋

首先, 聚合的普遍結構長成這樣:

{
  聚合: {
    <聚合名字自取>: {
      <聚合種類>: {
        <聚合欄位>: ""
      }
    }
  }
}

那麼以這次的目標來說 就是下面這樣

{
  聚合: {
    以形狀分類: {
      詞分類: {
        欄位: 形狀
      }
    }
  }
}

實際操作

❯ curl -s -H "Content-Type: Application/json" -XPOST "http://192.168.40.41:9200/demo/_search" -d '
{
  "size": 0,
  "query": {
    "bool": {}
  },
  "aggs": {
    "group_by_shape": {
      "terms": {
        "field": "shape.keyword"
      }
    }
  }
}
' | jq .


{
  "took": 177,
  "timed_out": false,
  "_shards": {
    "total": 3,
    "successful": 3,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 25,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "group_by_shape": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "rectangle",
          "doc_count": 9
        },
        {
          "key": "circle",
          "doc_count": 8
        },
        {
          "key": "triangle",
          "doc_count": 8
        }
      ]
    }
  }
}

可以看到最後結果的部分; 已經得到我們想要的分類囉!

這邊注意幾個地方

  1. size: 0: 因為我們在乎的是聚合後的結果, 而不是原始資料; 所以這邊size就可以等於0. 事實上, 如果您也有使用 grafana 或是 kibana, 也會發現他們也是這樣使用的.
  2. took: 177: 代表這次搜尋花了 177 毫秒.
  3. hits: 25: 代表總共有 25 顆球球.
  4. aggregations: 透過聚合得到的資料都會出現在這物件裡.
  5. buckets: 桶, 每個分類都是用桶子裝著 XD


接著再以顏色做分類

上面分類出來, 只能得到各形狀有幾顆球球; 但是我們還需要知道更細的分類
每個形狀裡面, 個別又有多少顏色的球?

這是我們想要得到的結果:

這時候, 有了上面分類的概念, 只要照著嵌套下去就行了~
所以說, 結構長成這樣:

{
  聚合: {
    以形狀分類: {
      詞分類: {
        欄位: 形狀
      },
      聚合: {
        以顏色分類: {
          詞分類: {
            欄位: 顏色
          }
        }
      }
    }
  }
}


實際操作

❯ curl -s -H "Content-Type: Application/json" -XPOST "http://192.168.40.41:9200/demo/_search" -d '
{
  "size": 0,
  "query": {
    "bool": {}
  },
  "aggs": {
    "group_by_shape": {
      "terms": {
        "field": "shape.keyword"
      },
      "aggs": {
        "group_by_color": {
          "terms": {
            "field": "color.keyword"
          }
        }
      }
    }
  }
}
' | jq .

{
  "took": 65,
  "timed_out": false,
  "_shards": {
    "total": 3,
    "successful": 3,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 25,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "group_by_shape": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "rectangle",
          "doc_count": 9,
          "group_by_color": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "blue",
                "doc_count": 5
              },
              {
                "key": "red",
                "doc_count": 3
              },
              {
                "key": "yellow",
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key": "circle",
          "doc_count": 8,
          "group_by_color": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "blue",
                "doc_count": 4
              },
              {
                "key": "red",
                "doc_count": 3
              },
              {
                "key": "yellow",
                "doc_count": 1
              }
            ]
          }
        },
        {
          "key": "triangle",
          "doc_count": 8,
          "group_by_color": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "yellow",
                "doc_count": 6
              },
              {
                "key": "blue",
                "doc_count": 2
              }
            ]
          }
        }
      ]
    }
  }
}

鏘鏘鏘~ 這樣就達到我們要的結果了~ 如果說每個球球身上還有分數的話, 還可以繼續嵌套下去將它計算出來唷!

所以上面得答案依序是

  1. 這一坨球球裡面有哪些形狀? rectangle, triangle, circle
  2. 每個形狀有幾顆球球? rectangle:9, triangle:8, circle: 8
  3. 每個形狀, 有幾個不同顏色的球球?
rectangle-blue: 5, rectangle-red: 3, rectangle-yellow: 1
triangle-blue: 2, triangle-red: 0, triangle-yellow: 6
circle-blue: 4, circle-red: 3, circle-yellow: 1

分組聚合就到這邊告一段落囉, 謝謝大家.