ElasticSearch - 聚合 aggs

古古

2018/07/23


聚合概念 aggs

  • ElasticSearch 除了致力於搜索之外,也提供了聚合實時分析數據的功能
    • 如果把搜索比喻為大海撈針(從海量的文檔中找出符合條件的那一個),那麼聚合就是去分析大海中的針們的特性,像是
      • 在大海里有多少針?
      • 針的平均長度是多少?
      • 按照針的製造商來劃分,針的長度中位值是多少?
      • 每月加入到海中的針有多少?
      • 這裏面有異常的針麼?
    • 因此透過聚合,我們可以得到一個數據的概覽,聚合能做的是分析和總結全套的數據,而不是查找單個文檔(這是搜索做的事)
  • 聚合允許我們向數據提出一些複雜的問題,雖然他的功能完全不同於搜索,但他們其實使用了相同的數據結構,這表示聚合的執行速度很快,並且就像搜索一樣幾乎是實時的
    • 並且由於聚合和搜索是使用同樣的數據結構,因此聚合和搜索可以是一起執行的
    • 這表示我們可以在一次 json 請求裡,同時對相同的數據進行 搜索/過濾 + 分析,兩個願望一次滿足
  • 聚合的兩個主要的概念,分別是 桶 和 指標
    • 桶(Buckets): 滿足特定條件的文檔的集合
      • 當聚合開始被執行,每個文檔會決定符合哪個桶的條件,如果匹配到,文檔將放入相應的桶並接着進行聚合操作
        • 像是一個員工屬於男性桶或者女性桶,日期 2014-10-28 屬於十月桶,也屬於 2014 年桶
      • 桶可以被嵌套在其他桶裏面
        • 像是北京能放在中國桶裡,而中國桶能放在亞洲桶裡
      • Elasticsearch 提供了很多種類型的桶,像是時間、最受歡迎的詞、年齡區間、地理位置桶等等,不過他們在根本上都是通過同樣的原理進行操作,也就是基於條件來劃分文檔,一個文檔只要符合條件,就可以加入那個桶,因此一個文檔可以同時加入很多桶
    • 指標(Metrics) : 對桶內的文檔進行統計計算
      • 桶能讓我們劃分文檔到有意義的集合, 但是最終我們需要的是對這些桶內的文檔進行一些指標的計算
      • 指標通常是簡單的數學運算(像是min、max、avg、sum),而這些是通過當前桶中的文檔的值來計算的,利用指標能讓你計算像平均薪資、最高出售價格、95 % 的查詢延遲這樣的數據
    • aggs 聚合的模板
      • 當 query 和 aggs 一起存在時,會先執行 query 的主查詢,主查詢 query 執行完後會搜出一批結果,而這些結果才會被拿去 aggs 拿去做聚合
        • 另外要注意 aggs 後面會先接一層自定義的這個聚合的名字,然後才是接上要使用的聚合桶
        • 如果有些情況不在意查詢結果是什麼,而只在意 aggs 的結果,可以把 size 設為 0,如此可以讓返回的 hits 結果集是 0,加快返回的速度
      • 一個 aggs 裡可以有很多個聚合,每個聚合彼此間都是獨立的,因此可以一個聚合拿來統計數量、一個聚合拿來分析數據、一個聚合拿來計算標準差…,讓一次搜索就可以把想要做的事情一次做完
        • 像是此例就定義了 3 個聚合,分別是 custom_name1、custom_name2、custom_name3
      • aggs 可以嵌套在其他的 aggs裡面,而嵌套的桶能作用的文檔集範圍,是外層的桶所輸出的結果集
        GET mytest/doc/_search
        {
            "query": { ... },
            "size": 0,
            "aggs": {
                "custom_name1": {  //aggs後面接著的是一個自定義的name
                	"桶": { ... }  //再來才是接桶
                },
                "custom_name2": {  //一個aggs裡可以有很多聚合
                    "桶": { ... }
                },
                "custom_name3": {
                    "桶": {
                       .....
                    },
                    "aggs": {  //aggs可以嵌套在別的aggs裡面
                        "in_name": { //記得使用aggs需要先自定義一個name
                            "桶": { ... } //in_name的桶作用的文檔是custom_name3的桶的結果
                        }
                    }
                }
            }
        }
        
      • 結果
        {
            "hits": {
                "total": 8,
                "max_score": 0,
                "hits": [] //因為size設為0,所以沒有查詢結果返回
            },
            "aggregations": {
                "custom_name1": {
                    ...
                },
                "custom_name2": {
                    ...
                },
                "custom_name3": {
                    ... ,
                    "in_name": {
                       ....
                    }
                }
            }
        }
        

terms桶

  • 功能 : 針對某個 field 的值進行分組,field 有幾種值就分成幾組
  • terms 桶在進行分組時,會爲此 field 中的每種值創建一個新的桶
  • 要注意此 “terms桶” 和平常用在主查詢 query 中的 “查找terms” 是不同的東西
  • 具體實例
    • 首先插入幾筆數據,其中 color 是一個 keyword 類型
      { "color": "red" }
      { "color": "green" }
      { "color": ["red", "blue"] }
      
    • 執行 terms 桶聚合
      GET mytest/doc/_search
      {
          "query": {
              "match_all": {}
          },
          "size": 0,
          "aggs": {
              "my_name": {
                  "terms": {
                      "field": "color", //使用color來進行分組
                  }
              }
          }
      }
      
    • 結果
      • 因為 color 總共有 3 種值,red、blue、green,所以 terms 桶為他們產生了 3 個 bucket,並計算了每個 bucket 中符合的文檔有哪些
      • bucket 和 bucket 間是獨立的,也就是說一個文檔可以同時符合好幾個 bucket,像是 {"color": ["red", "blue"]} 就同時符合了 red 和 blue bucket
        "aggregations": {
            "my_name": {
                "doc_count_error_upper_bound": 0,
                "sum_other_doc_count": 0,
                "buckets": [  //terms桶產生的buckets數組,裡面每個json對象都是一個bucket
                    {
                        "key": "blue",
                        "doc_count": 1
                    },
                    {
                        "key": "red",
                        //表示color為red的文檔有2個,此例中就是 {"color": "red"} 和 {"color": ["red", "blue"]}這兩個文檔
                        "doc_count": 2
                    },
                    {
                        "key": "green",
                        "doc_count": 1
                    }
                ]
            }
        }
        
    • 可以在參數帶上 min_doc_count 限制當 doc 數大於等於 n 筆時才會被搜出來,也可以帶上 size 參數,指名要顯示 bucket 中多少筆數據,默認是 10
      GET mytest/doc/_search
      {
          "query": {
              "match_all": {}
          },
          "size": 0,
          "aggs": {
              "my_name": {
                  "terms": {
                      "field": "color",
                      "size": 100, //可以設置size,指定返回的桶數量,預設是10,如果總共桶數不超過100,那就會全部返回
                      "min_doc_count": 2 //bucket中只有當doc_count >= 2的人,才會被搜出來
                  }
              }
          }
      }
      
    • 結果
      "aggregations": {
          "my_name": {
              "doc_count_error_upper_bound": 0,
              "sum_other_doc_count": 0,
              "buckets": [ //因為設置了min_doc_count:2,所有只有red桶被搜出來
                  {
                      "key": "red",
                      "doc_count": 2
                  }
              ]
          }
      }
      
  • 具體實例二
    • 將 terms 桶搭配度量指標(avg、min、max、sum…)一起使用
      • 其實度量指標也可以看成一種"桶",他可以和其他正常的桶們進行嵌套作用,差別只在指標關注的是這些文檔中的某個數值的統計,而桶關注的是文檔
    • 首先準備數據,color 一樣為 keyword 類型,而 price 為 integer 類型
      { "color": "red", "price": 100 }
      { "color": "green", "price": 500 }
      { "color": ["red", "blue"], "price": 1000 }
      
    • 將 avg 指標嵌套在 terms 桶裡一起使用
      GET mytest/doc/_search
      {
          "query": {
              "match_all": {}
          },
          "size": 0,
          "aggs": {
              "my_name": {
                  "terms": {
                      "field": "color"
                  },
                  "aggs": {  //嵌套兩個指標avg、min在terms桶中
                      "my_avg_price": { //my_avg_price計算每個bucket的平均price
                          "avg": {
                              "field": "price"
                          }
                      },
                      "my_min_price": { //my_min_price計算每個bucket中的最小price
                      	"min": {
                              "field": "price"
                          }
                      }
                  }
              }
          }
      }
      
    • 結果
      "aggregations": {
          "my_name": {
              "doc_count_error_upper_bound": 0,
              "sum_other_doc_count": 0,
              "buckets": [ //terms桶中的每個bucket都會計算avg和min兩個指標
                  {
                      "key": "blue",
                      "doc_count": 1,
                      "my_avg_price": { //avg指標
                          "value": 1000
                      },
                      "my_min_price": { //min指標
                          "value": 100
                      }
                  },
                  {
                      "key": "red",
                      "doc_count": 2,
                      //avg指標計算的值,因為符合color為red的文檔有兩筆,所以平均price為100+1000/2 = 550
                      "my_avg_price": {
                          "value": 550
                      },
                      "my_min_price": {
                          "value": 100
                      }
                  },
                  {
                      "key": "green",
                      "doc_count": 1,
                      "my_avg_price": {
                          "value": 500
                      },
                      "my_min_price": {
                          "value": 500
                      }
                  }
              ]
          }
      }
      
  • 具體實例三
    • 使用 terms 桶時,只聚合感興趣的 bucket,其他 bucket 都不聚合
      • 使用 terms 桶支持的 includeexclude 參數來設定哪些 bucket 的值要聚合,哪些 bucket 的值不聚合
        • 當 include 和 exclude 的規則有重疊的部份時,優先使用 exclude 的
      • includeexclude 都支持設置特定值、數組、以及正則
    • 準備數據
      { "color": ["red", "green"] }
      { "color": ["red", "blue"] }
      
    • 聚合時只聚合我們感興趣的 bucket,也就是 color 為 red 的文檔有多少,其他的 bucket 不聚合
      GET mytest/doc/_search
      {
          "query": {
              "match_all": {}
          },
          "size": 0,
          "aggs": {
              "my_name": {
                  "terms": {
                      "field": "color",
                      "include": "red"  //include也支持數組和正則
                  }
              }
          }
      }
      
    • 結果
      "aggregations": {
          "my_name": {
              "doc_count_error_upper_bound": 0,
              "sum_other_doc_count": 0,
              //本來是有三個bucket的,但是因為設置了include,所以只聚合計算了一個bucket red
              "buckets": [
                  {
                      "key": "red",
                      "doc_count": 2
                  }
              ]
          }
      }
      

filter桶

  • 功能 : 一個用來過濾的桶
  • 此處的 “filter 桶” 和用在主查詢 query 的 “過濾 filter” 的用法是一模一樣的,都是過濾
  • 不過差別是 “filter 桶” 會自己給創建一個新的桶,而不會像 “過濾 filter” 一樣依附在 query 下
    • 因為 filter 桶畢竟還是一個聚合桶,因此他可以和別的桶進行嵌套,但他不是依附在別的桶上
  • 具體實例
    • 取得 color 為 red 或是 blue 的文檔
      GET mytest/doc/_search
      {
          "query": {
              "match_all": {}
          },
          "size": 0,
          "aggs": {
              "my_name": {
                  "filter": { //此處是一個filter桶,因為他用法跟一般的過濾filter一樣,所以也能使用bool嵌套
                      "bool": {
                          "must": {
                              "terms": { //注意此terms是查找terms,不是terms桶
                                  "color": [ "red", "blue" ]
                              }
                          }
                      }
                  }
              }
          }
      }
      
    • 結果
      "aggregations": {
          "my_name": {
              "doc_count": 2 //filter桶計算出來的文檔數量
          }
      }
      
  • 具體實例二
    • filter 桶和 terms 桶嵌套使用,先過濾出 color 為 red 或 blue 的文檔,再對這些文檔進行 color 分組
      GET mytest/doc/_search
      {
          "query": {
              "match_all": {}
          },
          "size": 0,
          "aggs": {
              "my_name": { //my_name聚合
                  "filter": { //filter桶
                      "bool": {
                          "must": {
                              "terms": {
                                  "color": [ "red", "blue" ]
                              }
                          }
                      }
                  },
                  "aggs": {
                      "my_name2": { //my_name2聚合,嵌套在my_name聚合裡
                          "terms": { //terms桶
                              "field": "color"
                          }
                      }
                  }
              }
          }
      }
      
    • 結果
      • 因為 terms 桶嵌套在 filter 桶內,所以 query 查詢出來的文檔們會先經過 filter 桶,如果符合 filter 桶,才會進入到 terms 桶內
      • 此處通過 filter 桶的文檔只有兩筆,分別是 {"color": "red"}以及{"color": ["red", "blue"]},所以 terms 桶只會對這兩筆文檔做分組
      • 這也是為什麼 terms 桶裡沒有出現 color 為 green 的分組,因為這個文檔在 filter 桶就被擋下來了
        "aggregations": {
            "my_name": {
                "doc_count": 2, //filter桶計算的數量,通過此處的文檔只有2筆
                "my_name2": {
                    "doc_count_error_upper_bound": 0,
                    "sum_other_doc_count": 0,
                    "buckets": [
                        {
                            "key": "red",
                            "doc_count": 2  //terms桶計算的數量
                        },
                        {
                            "key": "blue",
                            "doc_count": 1  //terms桶計算的數量
                        }
                    ]
                }
            }
        }
        

多桶排序

  • terms 桶、histogram 桶、data_histogram 桶這些桶屬於多值桶,也就是說他們會動態生成很多桶,對於這些生成出來的桶們,ES 默認會使用 doc_value 進行降序排序,也就是說哪個生成桶的 doc_value 文檔數較多,哪個生成桶就排在前面
    • 如果想要改變這個生成桶與生成桶之間的排序,可以在使用 terms 桶、histogram 桶、data_histogram 桶時,使用 order 進行排序
    • order 支持的參數
      • _count : 按照文檔數排序
      • _term : 按照每個桶的字符串值的字母順序排序
  • 具體實例
    • 準備數據,color 是 keyword 類型
      { "color": "red", "price": 100 }
      { "color": ["red", "blue"], "price": 1000 }
      
    • 使用 terms 桶進行分組,並且規定按照桶的字母順序升序,因此 a 生成桶會排在最前面而 z 生成桶會排在最後面
      GET mytest/doc/_search
      {
          "query": {
              "match_all": {}
          },
          "size": 0,
          "aggs": {
              "my_name": {
                  "terms": {
                      "field": "color",
                      "order": {
                          "_term": "asc"
                      }
                  }
              }
          }
      }
      
    • 結果
      "aggregations": {
          "my_name": {
              "doc_count_error_upper_bound": 0,
              "sum_other_doc_count": 0,
              "buckets": [
                  {
                      "key": "blue",
                      "doc_count": 1
                  },
                  {
                      "key": "red",
                      "doc_count": 2
                  }
              ]
          }
      }