1. 程式人生 > >實時搜尋引擎Elasticsearch(4)——Aggregations (聚合)API的使用

實時搜尋引擎Elasticsearch(4)——Aggregations (聚合)API的使用

上一篇部落格介紹了ES中的簡單查詢API的使用,本篇將介紹ES提供的聚合API的使用。ES提供的聚合功能可以用來進行簡單的資料分析。本文仍然以上一篇提供的資料為例來講解。資料如下:

studentNo name male age birthday classNo address isLeader
1 劉備 24 1985-02-03 1 湖南省長沙市 true
2 關羽 22 1987-08-23 2 四川省成都市 false
3 糜夫人 19 1990-06-12 1 上海市 false
4 張飛 20 1989-07-30 3 北京市 false
5 諸葛亮 18 1992-04-27 2 江蘇省南京市 true
6 孫尚香 16 1994-05-21 3 false
7 馬超 19 1991-10-20 1 黑龍江省哈爾濱市 false
8 趙雲 23 1986-10-26 2 浙江省杭州市 false

本文的主要內容有:

  1. metric API的使用
  2. bucketing API的使用
  3. 兩類API的巢狀使用

1. 聚合API

ES中的Aggregations API是從Facets功能基礎上發展而來,官網正在進行替換計劃,建議使用者使用Aggregations API,而不是Facets API。ES中的聚合上可以分為下面兩類:

  1. metric(度量)聚合:度量型別聚合主要針對的number型別的資料,需要ES做比較多的計算工作
  2. bucketing(桶)聚合:劃分不同的“桶”,將資料分配到不同的“桶”裡。非常類似sql中的group語句的含義。

metric既可以作用在整個資料集上,也可以作為bucketing的子聚合作用在每一個“桶”中的資料集上。當然,我們可以把整個資料集合看做一個大“桶”,所有的資料都分配到這個大“桶”中。

ES中的聚合API的呼叫格式如下:

"aggregations" : {                  // 表示聚合操作,可以使用aggs替代
    "<aggregation_name>" : {        // 聚合名,可以是任意的字串。用做響應的key,便於快速取得正確的響應資料。
        "<aggregation_type>" : {    // 聚合類別,就是各種型別的聚合,如min等
            <aggregation_body>      // 聚合體,不同的聚合有不同的body
        }
        [,"aggregations" : { [<sub_aggregation>]+ } ]? // 巢狀的子聚合,可以有0或多個
    }
    [,"<aggregation_name_2>" : { ... } ]* // 另外的聚合,可以有0或多個
}

1.1 度量型別(metric)聚合

(1)Min Aggregation

最小值查詢,作用於number型別欄位上。查詢2班最小的年齡值。

curl -XPOST "192.168.1.101:9200/student/student/_search" -d 
'
{
  "query": {         // 可以先使用query查詢得到需要的資料集
    "term": {
      "classNo": "2"
    }
  },
  "aggs": {
    "min_age": {
      "min": {
        "field": "age"
      }
    }
  }
}
'

查詢結果為:

{
  "took": 19,                     // 前面部分資料與普通的查詢資料相同
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 1.4054651,
    "hits": [
      {
        "_index": "student",
        "_type": "student",
        "_id": "2",
        "_score": 1.4054651,
        "_source": {
          "studentNo": "2",
          "name": "關羽",
          "male": "男",
          "age": "22",
          "birthday": "1987-08-23",
          "classNo": "2",
          "isLeader": "false"
        }
      },
      {
        "_index": "student",
        "_type": "student",
        "_id": "8",
        "_score": 1,
        "_source": {
          "studentNo": "8",
          "name": "趙雲",
          "male": "男",
          "age": "23",
          "birthday": "1986-10-26",
          "classNo": "2",
          "isLeader": "false"
        }
      },
      {
        "_index": "student",
        "_type": "student",
        "_id": "5",
        "_score": 0.30685282,
        "_source": {
          "studentNo": "5",
          "name": "諸葛亮",
          "male": "男",
          "age": "18",
          "birthday": "1992-04-27",
          "classNo": "2",
          "isLeader": "true"
        }
      }
    ]
  },
  "aggregations": {                    // 聚合結果
    "min_age": {                       // 前面輸入的聚合名
      "value": 18,                     // 聚合後的資料
      "value_as_string": "18.0"
    }
  }
}

上面的聚合查詢有兩個要注意的點:

  1. 可以通過query先過濾資料
  2. 返回的結果會包含聚合操作所作用的資料全集

有時候我們對作用的資料全集並不太敢興趣,我們僅僅需要最終的聚合結果。可以通過查詢型別(search_type)引數來實現這個需求。下面查詢出來的資料量會大大減少,ES內部也會在查詢時減少一些耗時的步驟,所以查詢效率會提高。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d     // 注意這裡的search_type=count
'
{
  "query": {             // 可以先使用query查詢得到需要的資料集
    "term": {
      "classNo": "2"
    }
  },
  "aggs": {
    "min_age": {
      "min": {
        "field": "age"
      }
    }
  }
}
'

本次的查詢結果為:

{
...

"aggregations": {                    // 聚合結果
    "min_age": {                       // 前面輸入的聚合名
      "value": 18,                     // 聚合後的資料
      "value_as_string": "18.0"
    }
  }
}

(2)Max Aggregation

最大值查詢。下面查詢2班最大的年齡值,查詢結果為23。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d 
'
{
  "query": {
    "term": {
      "classNo": "2"
    }
  },
  "aggs": {
    "max_age": {
      "max": {
        "field": "age"
      }
    }
  }
}
'

(3)Sum Aggregation

數值求和。下面統計查詢2班的年齡總和,查詢結果為63。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d 
'
{
  "query": {
    "term": {
      "classNo": "2"
    }
  },
  "aggs": {
    "sum_age": {
      "sum": {
        "field": "age"
      }
    }
  }
}
'

(4)Avg Aggregation

計算平均值。下面計算查詢2班的年齡平均值,結果為21。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d 
'
{
  "query": {
    "term": {
      "classNo": "2"
    }
  },
  "aggs": {
    "avg_age": {
      "avg": {
        "field": "age"
      }
    }
  }
}
'

(5)Stats Aggregation

統計查詢,一次性統計出某個欄位上的常用統計值。下面對整個學校的學生進行簡單地統計。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d 
'
{
  "aggs": {
    "stats_age": {
      "stats": {
        "field": "age"
      }
    }
  }
}
'

查詢結果為:

{
  ...                     // 次要資料省略

  "aggregations": {
    "stats_age": {
      "count": 8,        // 含有年齡資料的學生計數
      "min": 16,         // 年齡最小值
      "max": 24,         // 年齡最大值
      "avg": 20.125,     // 年齡平均值
      "sum": 161,        // 年齡總和
      "min_as_string": "16.0",
      "max_as_string": "24.0",
      "avg_as_string": "20.125",
      "sum_as_string": "161.0"
    }
  }
}

(6)Top hits Aggregation

取符合條件的前n條資料記錄。下面查詢全校年齡排在前2位的學生,僅需返回學生姓名和年齡。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d 
{
  "aggs": {
    "top_age": {
      "top_hits": {
        "sort": [               // 排序
          {
            "age": {            // 按年齡降序
              "order": "desc"
            }
          }
        ],
        "_source": {
          "include": [           // 指定返回欄位
            "name",
            "age"
          ]
        },
        "size": 2                 // 取前2條資料
      }
    }
  }
}

返回結果為:

{
  ...

  "aggregations": {
    "top_age": {
      "hits": {
        "total": 9,
        "max_score": null,
        "hits": [
          {
            "_index": "student",
            "_type": "student",
            "_id": "1",
            "_score": null,
            "_source": {
              "name": "劉備",
              "age": "24"
            },
            "sort": [
              24
            ]
          },
          {
            "_index": "student",
            "_type": "student",
            "_id": "8",
            "_score": null,
            "_source": {
              "name": "趙雲",
              "age": "23"
            },
            "sort": [
              23
            ]
          }
        ]
      }
    }
  }
}

1.2 桶型別(bucketing)聚合

(1)Terms Aggregation

按照指定的1或多個欄位將資料劃分成若干個小的區間,計算落在每一個區間上記錄數量,並按指定順序進行排序。下面統計每個班的學生數,並按學生數從大到小排序,取學生數靠前的2個班級。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d 
'
{
  "aggs": {
    "terms_classNo": {
      "terms": {
        "field": "classNo",            // 按照班號進行分組
        "order": {                     // 按學生數從大到小排序
          "_count": "desc"
        },
        "size": 2                      // 取前兩名
      }
    }
  }
}
'

值得注意的,取得的前2名的學生數實際上是一個近似值,ES的實現方式參見這裡。如果想要取得精確值,可以不指定size值,使其進行一次全排序,然後在程式中自行去取前2條記錄。當然,這樣做會使得ES做大量的排序運算工作,效率比較差。

(2)Range Aggregation

自定義區間範圍的聚合,我們可以自己手動地劃分區間,ES會根據劃分出來的區間將資料分配不同的區間上去。下面將全校學生按照年齡劃分為5個區間段:16歲以下、16~18、19~21、22~24、24歲以上,要求統計每一個年齡段內的學生數。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d 
'
{
  "aggs": {
    "range_age": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "to": 15
          },
          {
            "from": "16",
            "to": "18"
          },
          {
            "from": "19",
            "to": "21"
          },

          {
            "from": "22",
            "to": "24"
          },
          {
            "from": "25"
          }
        ]
      }
    }
  }
}
'

(3)Date Range Aggregation

時間區間聚合專門針對date型別的欄位,它與Range Aggregation的主要區別是其可以使用時間運算表示式。主要包括+(加法)運算、-(減法)運算和/(四捨五入)運算,每種運算都可以作用在不同的時間域上面,下面是一些時間運算表示式示例。

  • now+10y:表示從現在開始的第10年。
  • now+10M:表示從現在開始的第10個月。
  • 1990-01-10||+20y:表示從1990-01-01開始後的第20年,即2010-01-01。
  • now/y:表示在年位上做舍入運算。今天是2015-09-06,則這個表示式計算結果為:2015-01-01。說好的rounding運算呢?結果是做的flooring運算,不知道為啥,估計是我理解錯了-_-!!

下面查詢25年前及更早出生的學生數。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d 
'
{
  "aggs": {
    "range_age": {
      "date_range": {
        "field": "birthday",
        "ranges": [
          {
            "to": "now-25y"
          }
        ]
      }
    }
  }
}
'

(4)Histogram Aggregation

直方圖聚合,它將某個number型別欄位等分成n份,統計落在每一個區間內的記錄數。它與前面介紹的Range聚合非常像,只不過Range可以任意劃分區間,而Histogram做等間距劃分。既然是等間距劃分,那麼引數裡面必然有距離引數,就是interval引數。下面按學生年齡統計各個年齡段內的學生數量,分隔距離為2歲。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d 
'
{
  "aggs": {
    "histogram_age": {
      "histogram": {
        "field": "age",
        "interval": 2,               // 距離為2
        "min_doc_count": 1           // 只返回記錄數量大於等於1的區間
      }
    }
  }
}
'

(5)Date Histogram Aggregation

時間直方圖聚合,專門對時間型別的欄位做直方圖聚合。這種需求是比較常用見得的,我們在統計時,通常就會按照固定的時間斷(1個月或1年等)來做統計。下面統計學校中同一年出生的學生數。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d 
'
{
  "aggs": {
    "data_histogram_birthday": {
      "date_histogram": {
        "field": "birthday",
        "interval": "year",              // 按年統計
        "format": "yyyy"                 // 返回結果的key的格式
      }
    }
  }
}
'

返回結果如下,可以看到由於上面的”format”: “yyyy”,所以返回的key_as_string只返回年的資訊。

{
  "buckets": [
    {
      "key_as_string": "1985",
      "key": 473385600000,
      "doc_count": 1
    },
    {
      "key_as_string": "1986",
      "key": 504921600000,
      "doc_count": 1
    },
    {
      "key_as_string": "1987",
      "key": 536457600000,
      "doc_count": 1
    },
    {
      "key_as_string": "1989",
      "key": 599616000000,
      "doc_count": 1
    },
    {
      "key_as_string": "1990",
      "key": 631152000000,
      "doc_count": 1
    },
    {
      "key_as_string": "1991",
      "key": 662688000000,
      "doc_count": 1
    },
    {
      "key_as_string": "1992",
      "key": 694224000000,
      "doc_count": 1
    },
    {
      "key_as_string": "1994",
      "key": 757382400000,
      "doc_count": 1
    }
  ]
}

(6)Missing Aggregation

值缺損聚合,它是一類單桶聚合,也就是最終只會產生一個“桶”。下面統計學生資訊中位址列缺損的記錄數量。由於只有學號為6的孫尚香的地址缺損,所以統計值為1。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d 
'
{
  "aggs": {
    "missing_address": {
      "missing": {
        "field": "address"
      }
    }
  }
}
'

1.3 巢狀使用

前面已經說過,聚合操作是可以巢狀使用的。通過巢狀,可以使得metric型別的聚合操作作用在每一“桶”上。我們可以使用ES的巢狀聚合操作來完成稍微複雜一點的統計功能。下面統計每一個班裡最大的年齡值。

curl -XPOST "192.168.1.101:9200/student/student/_search?search_type=count" -d
'
{
  "aggs": {
    "missing_address": {
      "terms": {
        "field": "classNo"
      },
      "aggs": {                 // 在這裡巢狀新的子聚合
        "max_age": {
          "max": {              // 使用max聚合
            "field": "age"
          }
        }
      }
    }
  }
}
'

返回結果如下:

{
  "buckets": [
    {
      "key": "1",               // key是班級號
      "doc_count": 3,           // 每個班級內的人數
      "max_age": {              // 這裡是我們指定的子聚合名
        "value": 24,            // 每班的年齡值
        "value_as_string": "24.0"
      }
    },
    {
      "key": "2",
      "doc_count": 3,
      "max_age": {
        "value": 23,
        "value_as_string": "23.0"
      }
    },
    {
      "key": "3",
      "doc_count": 1,
      "max_age": {
        "value": 20,
        "value_as_string": "20.0"
      }
    },
    {
      "key": "4",
      "doc_count": 1,
      "max_age": {
        "value": 16,
        "value_as_string": "16.0"
      }
    }
  ]
}

2. 總結

本文介紹了ES中的一些常用的聚合API的使用,包括metric、bucketing以及它們的巢狀使用方法。掌握了這些API就可以完成簡單的資料統計功能,更多的API詳見官方文件。前面的部落格中都是介紹了ES的Rest API,接下來的文章中將會介紹Java API的使用,使用Java API可以實現前面介紹的所有API的功能。