Elasticsearch權威指南 java API deme文檔地址: https://es.xiaoleilu.com/html
Elasticsearch有一個功能叫作 聚合(aggregations) ,它容許你在數據上生成複雜的分析統計。它很像SQL中的 GROUP BY 可是功能更強大。java
Aggregations種類分爲:web
和查詢DSL同樣,聚合(Aggregations)也擁有一種可組合(Composable)的語法:獨立的功能單元能夠被混合在一塊兒來知足你的需求。這意味着須要學習的基本概念雖然很少,可是它們的組合方式是幾近無窮的。數據庫
爲了掌握聚合,你只須要了解兩個主要概念:
Buckets(桶)
知足某個條件的文檔集合。
Metrics(指標)
爲某個桶中的文檔計算獲得的統計信息。api
就是這樣!每一個聚合只是簡單地由一個或者多個桶,零個或者多個指標組合而成。能夠將它粗略地轉換爲SQL:微信
[java] view plain copyapp
以上的COUNT(color)就至關於一個指標。GROUP BY color則至關於一個桶。
桶和SQL中的組(Grouping)擁有類似的概念,而指標則與COUNT(),SUM(),MAX()等類似。elasticsearch
讓咱們仔細看看這些概念。函數
一個桶就是知足特定條件的一個文檔集合:學習
隨着聚合被執行,每份文檔中的值會被計算來決定它們是否匹配了桶的條件。若是匹配成功,那麼該文檔會被置入該桶中,同時聚合會繼續執行。
桶也可以嵌套在其它桶中,能讓你完成層次或者條件劃分這些需求。好比,Cincinnati能夠被放置在Ohio州這個桶中,而整個Ohio州則可以被放置在美國這個桶中。
ES中有不少類型的桶,讓你能夠將文檔經過多種方式進行劃分(按小時,按最流行的詞條,按年齡區間,按地理位置,以及更多)。可是從根本上,它們都根據相同的原理運做:按照條件對文檔進行劃分。
桶可以讓咱們對文檔進行有意義的劃分,可是最終咱們仍是須要對每一個桶中的文檔進行某種指標計算。分桶是達到最終目的的手段:提供了對文檔進行劃分的方法,從而讓你可以計算須要的指標。
多數指標僅僅是簡單的數學運算(好比,min,mean,max以及sum),它們使用文檔中的值進行計算。在實際應用中,指標可以讓你計算例如平均薪資,最高出售價格,或者百分之95的查詢延遲。
一個聚合就是一些桶和指標的組合。一個聚合能夠只有一個桶,或者一個指標,或者每樣一個。在桶中甚至能夠有多個嵌套的桶。好比,咱們能夠將文檔按照其所屬國家進行分桶,而後對每一個桶計算其平均薪資(一個指標)。
由於桶是能夠嵌套的,咱們可以實現一個更加複雜的聚合操做:
此時,就可以獲得每一個<國家,性別,年齡>組合的平均薪資信息了。它能夠經過一個請求,一次數據遍從來完成
現有索引數據:
index:school type:student --------------------------------------------------- {"grade":"1", "class":"1", "name":"xiao 1"} {"grade":"1", "class":"1", "name":"xiao 2"} {"grade":"1", "class":"2", "name":"xiao 3"} {"grade":"1", "class":"2", "name":"xiao 4"} {"grade":"1", "class":"2", "name":"xiao 5"}
Java分組統計年級和班級學生個數,如SQL: SELECT grade,class,count(1) FROM student GROUP BY grade,class;
[java] view plain copy
運行完成輸出結果 --------------------------------------------------- 1年級有5個學生。 1年級2班有3個學生。
1年級1班有2個學生
實現一個SQL: SELECT sum(field) from table group by field2
使用:AggregationBuilders.sum("name").field("field");
[java] view plain copy
PUT /company
{
"mappings": {
"employee": {
"properties": {
"age": {
"type": "long"
},
"country": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"fielddata": true
},
"join_date": {
"type": "date"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"position": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"salary": {
"type": "long"
}
}
}
}
}
GET /company/employee/_search
{
"size": 0,
"aggs": {
"group_by_country": {
"terms": {
"field": "country"
},
"aggs": {
"group_by_join_date": {
"date_histogram": {
"field": "join_date",
"interval": "year"
},
"aggs": {
"avg_salary": {
"avg": {
"field": "salary"
}
}
}
}
}
}
}
}
public class EmployeeAggrApp {
@SuppressWarnings({ "unchecked", "resource" })
public static void main(String[] args) throws Exception {
Settings settings = Settings.builder()
.put("cluster.name", "elasticsearch")
.build();
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300));
SearchResponse searchResponse = client.prepareSearch("company")
.addAggregation(AggregationBuilders.terms("group_by_country").field("country")
.subAggregation(AggregationBuilders
.dateHistogram("group_by_join_date")
.field("join_date")
.dateHistogramInterval(DateHistogramInterval.YEAR)
.subAggregation(AggregationBuilders.avg("avg_salary").field("salary")))
)
.execute().actionGet();
Map<String, Aggregation> aggrMap = searchResponse.getAggregations().asMap();
StringTerms groupByCountry = (StringTerms) aggrMap.get("group_by_country");
Iterator<Bucket> groupByCountryBucketIterator = groupByCountry.getBuckets().iterator();
while(groupByCountryBucketIterator.hasNext()) {
Bucket groupByCountryBucket = groupByCountryBucketIterator.next();
System.out.println(groupByCountryBucket.getKey() + ":" + groupByCountryBucket.getDocCount());
Histogram groupByJoinDate = (Histogram) groupByCountryBucket.getAggregations().asMap().get("group_by_join_date");
Iterator<org.elasticsearch.search.aggregations.bucket.histogram.Histogram.Bucket> groupByJoinDateBucketIterator = groupByJoinDate.getBuckets().iterator();
while(groupByJoinDateBucketIterator.hasNext()) {
org.elasticsearch.search.aggregations.bucket.histogram.Histogram.Bucket groupByJoinDateBucket = groupByJoinDateBucketIterator.next();
System.out.println(groupByJoinDateBucket.getKey() + ":" +groupByJoinDateBucket.getDocCount());
Avg avg = (Avg) groupByJoinDateBucket.getAggregations().asMap().get("avg_salary");
System.out.println(avg.getValue());
}
}
client.close();
}
}
以球員信息爲例,player索引的player type包含5個字段,姓名,年齡,薪水,球隊,場上位置。
index的mapping爲:
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 |
|
索引中的所有數據:
首先,初始化Builder:
1 |
|
接下來舉例說明各類聚合操做的實現方法,由於在es的api中,多字段上的聚合操做須要用到子聚合(subAggregation),初學者可能找不到方法(網上資料比較少,筆者在這個問題上折騰了兩天,最後度了源碼才完全搞清楚T_T),後邊會特地說明多字段聚合的實現方法。另外,聚合後的排序也會單獨說明。
例如要計算每一個球隊的球員數,若是使用SQL語句,應表達以下:
select team, count(*) as player_count from player group by team;
ES的java api:
1 2 3 |
|
例如要計算每一個球隊每一個位置的球員數,若是使用SQL語句,應表達以下:
select team, position, count(*) as pos_count from player group by team, position;
ES的java api:
1 2 3 4 |
|
例如要計算每一個球隊年齡最大/最小/總/平均的球員年齡,若是使用SQL語句,應表達以下:
select team, max(age) as max_age from player group by team;
ES的java api:
1 2 3 4 |
|
例如要計算每一個球隊球員的平均年齡,同時又要計算總年薪,若是使用SQL語句,應表達以下:
select team, avg(age)as avg_age, sum(salary) as total_salary from player group by team;
ES的java api:
1 2 3 4 5 6 |
|
例如要計算每一個球隊總年薪,並按照總年薪倒序排列,若是使用SQL語句,應表達以下:
select team, sum(salary) as total_salary from player group by team order by total_salary desc;
ES的java api:
1 2 3 4 |
|
須要特別注意的是,排序是在TermAggregation處執行的,Order.aggregation函數的第一個參數是aggregation的名字,第二個參數是boolean型,true表示正序,false表示倒序。
默認狀況下,search執行後,僅返回10條聚合結果,若是想反悔更多的結果,須要在構建TermsBuilder 時指定size:
TermsBuilder teamAgg= AggregationBuilders.terms("team").size(15);
獲得response後:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
綜上,聚合操做主要是調用了SearchRequestBuilder的addAggregation方法,一般是傳入一個TermsBuilder,子聚合調用TermsBuilder的subAggregation方法,能夠添加的子聚合有TermsBuilder、SumBuilder、AvgBuilder、MaxBuilder、MinBuilder等常見的聚合操做。
從實現上來說,SearchRequestBuilder在內部保持了一個私有的 SearchSourceBuilder實例, SearchSourceBuilder內部包含一個List<AbstractAggregationBuilder>,每次調用addAggregation時會調用 SearchSourceBuilder實例,添加一個AggregationBuilder。
一樣的,TermsBuilder也在內部保持了一個List<AbstractAggregationBuilder>,調用addAggregation方法(來自父類addAggregation)時會添加一個AggregationBuilder。有興趣的讀者也能夠閱讀源碼的實現。
索引名稱必須是小寫的,不能用下劃線開頭,不能包含逗號:product,website,blog
爲何相似的數據放在一個索引,非相似的數據放不一樣索引
1. 手動指定document id
(1)根據應用狀況來講,是否知足手動指定document id的前提:
舉個例子,好比說,咱們如今在開發一個電商網站,作搜索功能,或者是OA系統,作員工檢索功能。這個時候,數據首先會在網站系統或者IT系統內部的數據庫中,會先有一份,此時就確定會有一個數據庫的primary key(自增加,UUID,或者是業務編號)。若是將數據導入到es中,此時就比較適合採用數據在數據庫中已有的primary key。