推薦博客:
阮一峰大神:http://www.ruanyifeng.com/blog/2017/08/elasticsearch.html
ElasticSearch 權威指南(中文版):https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
Elasticsearch教程:https://www.sojson.com/blog/81.html
ik分詞器下載地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
與數據庫的關系
安裝目錄的介紹
在單機環境下創建器群
一般我們要在單機狀態下創建器群需要復制好幾份 然后改端口啥的,es不需要,直接在后面加參數就ok,爽的一比
在windows平台下 , 一個是端口 一個是數據的存儲位置 ,成功之后會在es的根目錄創建一個node2文件夾
.\elasticsearch.bat -E http.port=8200 -E path.data=node2
安裝ik分詞器
確認好自己的es版本
下載地址:https://github.com/medcl/elasticsearch-analysis-ik
然后把下載好的zip包解壓到plugins文件夾下。
unzip elasticsearch-analysis-ik-6.3.0.zip -d ik-analyzer
然后重啟elasticsearch:
測試
大家先不管語法,我們先測試一波。
在kibana控制台輸入下面的請求:

POST _analyze { "analyzer": "ik_max_word", "text": "我是中國人" } 運行得到結果: { "tokens": [ { "token": "我", "start_offset": 0, "end_offset": 1, "type": "CN_CHAR", "position": 0 }, { "token": "是", "start_offset": 1, "end_offset": 2, "type": "CN_CHAR", "position": 1 }, { "token": "中國人", "start_offset": 2, "end_offset": 5, "type": "CN_WORD", "position": 2 }, { "token": "中國", "start_offset": 2, "end_offset": 4, "type": "CN_WORD", "position": 3 }, { "token": "國人", "start_offset": 3, "end_offset": 5, "type": "CN_WORD", "position": 4 } ] }
索引
增加索引
PUT heima { "settings": { "number_of_replicas": 1, "number_of_shards": 5 } }
索引庫demo 5個分片 1個備份
查看索引
GET /索引庫名
刪除索引
DELETE /索引庫名
表
創建表(映射配置)
索引有了,接下來肯定是添加數據。但是,在添加數據之前必須定義映射。
什么是映射?
映射是定義文檔的過程,文檔包含哪些字段,這些字段是否保存,是否索引,是否分詞等
PUT /索引庫名/_mapping/類型名稱 { "properties": { "字段名": { "type": "類型", "index": true, "store": true, "analyzer": "分詞器" } } } 類型名稱:就是前面將的type的概念,類似於數據庫中的不同表字段名:任意填寫 ,可以指定許多屬性,例如: type:類型,可以是text、long、short、date、integer、object等 index:是否索引,默認為true store:是否存儲,默認為false analyzer:分詞器,這里的ik_max_word即使用ik分詞器
示例:
PUT heima/_mapping/goods { "properties": { "title": { "type": "text", "analyzer": "ik_max_word" }, "images": { "type": "keyword", "index": "false" }, "price": { "type": "float" } } }
響應結果:
{
"acknowledged": true
}
查看表
GET /索引庫名/_mapping
字段屬性詳解
type
我們說幾個關鍵的:
-
String類型,又分兩種:
- text:可分詞,不可參與聚合
- keyword:不可分詞,數據會作為完整字段進行匹配,可以參與聚合
-
Numerical:數值類型,分兩類
- 基本數據類型:long、interger、short、byte、double、float、half_float
- 浮點數的高精度類型:scaled_float
- 需要指定一個精度因子,比如10或100。elasticsearch會把真實值乘以這個因子后存儲,取出時再還原。
-
Date:日期類型
elasticsearch可以對日期格式化為字符串存儲,但是建議我們存儲為毫秒值,存儲為long,節省空間。
index
index影響字段的索引情況。
- true:字段會被索引,則可以用來進行搜索。默認值就是true
- false:字段不會被索引,不能用來搜索
index的默認值就是true,也就是說你不進行任何配置,所有字段都會被索引。
但是有些字段是我們不希望被索引的,比如商品的圖片信息,就需要手動設置index為false。
store
是否將數據進行額外存儲。
在學習lucene和solr時,我們知道如果一個字段的store設置為false,那么在文檔列表中就不會有這個字段的值,用戶的搜索結果中不會顯示出來。
但是在Elasticsearch中,即便store設置為false,也可以搜索到結果。
原因是Elasticsearch在創建文檔索引時,會將文檔中的原始數據備份,保存到一個叫做_source
的屬性中。而且我們可以通過過濾_source
來選擇哪些要顯示,哪些不顯示。
而如果設置store為true,就會在_source
以外額外存儲一份數據,多余,因此一般我們都會將store設置為false,事實上,**store的默認值就是false。**
boost
激勵因子,這個與lucene中一樣
其它的不再一一講解,用的不多,大家參考官方文檔:
數據增刪改查
新增數據
POST /索引庫名/類型名 { "key":"value" } --------------------------------------------------------------- POST /heima/goods/ { "title":"小米手機", "images":"http://image.leyou.com/12479122.jpg", "price":2699.00 }
通過kibana查看數據
get _search { "query":{ "match_all":{} } } --------------------------------------------------------------- { "_index": "heima", "_type": "goods", "_id": "r9c1KGMBIhaxtY5rlRKv", "_version": 1, "_score": 1, "_source": { "title": "小米手機", "images": "http://image.leyou.com/12479122.jpg", "price": 2699 } }
_source
:源文檔信息,所有的數據都在里面。_id
:這條文檔的唯一標示,與文檔自己的id字段沒有關聯
自定義id
如果我們想要自己新增的時候指定id,可以這么做:
POST /索引庫名/類型/id值 { ... } ------------------------------------------------ POST /heima/goods/2 { "title":"大米手機", "images":"http://image.leyou.com/12479122.jpg", "price":2899.00 }
智能判斷
在學習Solr時我們發現,我們在新增數據時,只能使用提前配置好映射屬性的字段,否則就會報錯。
不過在Elasticsearch中並沒有這樣的規定。
事實上Elasticsearch非常智能,你不需要給索引庫設置任何mapping映射,它也可以根據你輸入的數據來判斷類型,動態添加數據映射。
測試一下:
POST /heima/goods/3 { "title":"超米手機", "images":"http://image.leyou.com/12479122.jpg", "price":2899.00, "stock": 200, "saleable":true } 我們額外添加了stock庫存,和saleable是否上架兩個字段。 來看結果: { "_index": "heima", "_type": "goods", "_id": "3", "_version": 1, "_score": 1, "_source": { "title": "超米手機", "images": "http://image.leyou.com/12479122.jpg", "price": 2899, "stock": 200, "saleable": true } }
在看下索引庫的映射關系:
{ "heima": { "mappings": { "goods": { "properties": { "images": { "type": "keyword", "index": false }, "price": { "type": "float" }, "saleable": { "type": "boolean" }, "stock": { "type": "long" }, "title": { "type": "text", "analyzer": "ik_max_word" } } } } } }
stock和saleable都被成功映射了。
修改數據
把剛才新增的請求方式改為PUT,就是修改了。不過修改必須指定id,
- id對應文檔存在,則修改
- id對應文檔不存在,則新增
比如,我們把id為3的數據進行修改:
PUT /heima/goods/3 { "title":"超大米手機", "images":"http://image.leyou.com/12479122.jpg", "price":3899.00, "stock": 100, "saleable":true } 結果: { "took": 17, "timed_out": false, "_shards": { "total": 9, "successful": 9, "skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 1, "hits": [ { "_index": "heima", "_type": "goods", "_id": "3", "_score": 1, "_source": { "title": "超大米手機", "images": "http://image.leyou.com/12479122.jpg", "price": 3899, "stock": 100, "saleable": true } } ] } }
刪除數據
刪除使用DELETE請求,同樣,需要根據id進行刪除:
DELETE /索引庫名/類型名/id值
查詢
我們從4塊來講查詢:
- 基本查詢
_source
過濾- 結果過濾
- 高級查詢
- 排序
基本查詢:
GET /索引庫名/_search { "query":{ "查詢類型":{ "查詢條件":"查詢條件值" } } }
這里的query代表一個查詢對象,里面可以有不同的查詢屬性
- 查詢類型:
- 例如:
match_all
,?match
,term
?,?range
等等
- 例如:
- 查詢條件:查詢條件會根據類型的不同,寫法也有差異,后面詳細講解
查詢所有(match_all)
GET /heima/_search { "query":{ "match_all": {} } }
query
:代表查詢對象match_all
:代表查詢所有
結果:
{ "took": 2, "timed_out": false, "_shards": { "total": 3, "successful": 3, "skipped": 0, "failed": 0 }, "hits": { "total": 2, "max_score": 1, "hits": [ { "_index": "heima", "_type": "goods", "_id": "2", "_score": 1, "_source": { "title": "大米手機", "images": "http://image.leyou.com/12479122.jpg", "price": 2899 } }, { "_index": "heima", "_type": "goods", "_id": "r9c1KGMBIhaxtY5rlRKv", "_score": 1, "_source": { "title": "小米手機", "images": "http://image.leyou.com/12479122.jpg", "price": 2699 } } ] } }
- took:查詢花費時間,單位是毫秒
- time_out:是否超時
- _shards:分片信息
- hits:搜索結果總覽對象
- total:搜索到的總條數
- max_score:所有結果中文檔得分的最高分
- hits:搜索結果的文檔對象數組,每個元素是一條搜索到的文檔信息
- _index:索引庫
- _type:文檔類型
- _id:文檔id
- _score:文檔得分
- _source:文檔的源數據
匹配查詢(match)
PUT /heima/goods/3 { "title":"小米電視4A", "images":"http://image.leyou.com/12479122.jpg", "price":3899.00 }
現在,索引庫中有2部手機,1台電視:
- or關系
match
類型查詢,會把查詢條件進行分詞,然后進行查詢,多個詞條之間是or的關系
GET /heima/_search { "query":{ "match":{ "title":"小米電視" } } } 結果: "hits": { "total": 2, "max_score": 0.6931472, "hits": [ { "_index": "heima", "_type": "goods", "_id": "tmUBomQB_mwm6wH_EC1-", "_score": 0.6931472, "_source": { "title": "小米手機", "images": "http://image.leyou.com/12479122.jpg", "price": 2699 } }, { "_index": "heima", "_type": "goods", "_id": "3", "_score": 0.5753642, "_source": { "title": "小米電視4A", "images": "http://image.leyou.com/12479122.jpg", "price": 3899 } } ] }
在上面的案例中,不僅會查詢到電視,而且與小米相關的都會查詢到,多個詞之間是or
的關系。
- and關系
某些情況下,我們需要更精確查找,我們希望這個關系變成and
,可以這樣做:
GET /heima/_search { "query":{ "match": { "title": { "query": "小米電視", "operator": "and" } } } } 結果: { "took": 2, "timed_out": false, "_shards": { "total": 3, "successful": 3, "skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 0.5753642, "hits": [ { "_index": "heima", "_type": "goods", "_id": "3", "_score": 0.5753642, "_source": { "title": "小米電視4A", "images": "http://image.leyou.com/12479122.jpg", "price": 3899 } } ] } }
本例中,只有同時包含小米
和電視
的詞條才會被搜索到。
- or和and之間?
在 or
與 and
間二選一有點過於非黑即白。 如果用戶給定的條件分詞后有 5 個查詢詞項,想查找只包含其中 4 個詞的文檔,該如何處理?將 operator 操作符參數設置成 and
只會將此文檔排除。
有時候這正是我們期望的,但在全文搜索的大多數應用場景下,我們既想包含那些可能相關的文檔,同時又排除那些不太相關的。換句話說,我們想要處於中間某種結果。
match
查詢支持 minimum_should_match
最小匹配參數, 這讓我們可以指定必須匹配的詞項數用來表示一個文檔是否相關。我們可以將其設置為某個具體數字,更常用的做法是將其設置為一個百分數
,因為我們無法控制用戶搜索時輸入的單詞數量:
GET /heima/_search { "query":{ "match":{ "title":{ "query":"小米曲面電視", "minimum_should_match": "75%" } } } }
本例中,搜索語句可以分為3個詞,如果使用and關系,需要同時滿足3個詞才會被搜索到。這里我們采用最小品牌數:75%,那么也就是說只要匹配到總詞條數量的75%即可,這里3*75% 約等於2。所以只要包含2個詞條就算滿足條件了。
多字段查詢(multi_match)
multi_match
與match
類似,不同的是它可以在多個字段中查詢
GET /heima/_search { "query":{ "multi_match": { "query": "小米", "fields": [ "title", "subTitle" ] } } }
本例中,我們會在title字段和subtitle字段中查詢小米
這個詞
詞條匹配(term)
term
查詢被用於精確值 匹配,這些精確值可能是數字、時間、布爾或者那些**未分詞**的字符串
GET /heima/_search { "query":{ "term":{ "price":2699.00 } } } ----------------------------------------------------------- { "took": 2, "timed_out": false, "_shards": { "total": 3, "successful": 3, "skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 1, "hits": [ { "_index": "heima", "_type": "goods", "_id": "r9c1KGMBIhaxtY5rlRKv", "_score": 1, "_source": { "title": "小米手機", "images": "http://image.leyou.com/12479122.jpg", "price": 2699 } } ] } }
多詞條精確匹配(terms)
terms
查詢和 term 查詢一樣,但它允許你指定多值進行匹配。如果這個字段包含了指定值中的任何一個值,那么這個文檔滿足條件:
GET /heima/_search { "query":{ "terms":{ "price":[2699.00,2899.00,3899.00] } } } 結果: { "took": 4, "timed_out": false, "_shards": { "total": 3, "successful": 3, "skipped": 0, "failed": 0 }, "hits": { "total": 3, "max_score": 1, "hits": [ { "_index": "heima", "_type": "goods", "_id": "2", "_score": 1, "_source": { "title": "大米手機", "images": "http://image.leyou.com/12479122.jpg", "price": 2899 } }, { "_index": "heima", "_type": "goods", "_id": "r9c1KGMBIhaxtY5rlRKv", "_score": 1, "_source": { "title": "小米手機", "images": "http://image.leyou.com/12479122.jpg", "price": 2699 } }, { "_index": "heima", "_type": "goods", "_id": "3", "_score": 1, "_source": { "title": "小米電視4A", "images": "http://image.leyou.com/12479122.jpg", "price": 3899 } } ] } }
結果過濾
默認情況下,elasticsearch在搜索的結果中,會把文檔中保存在_source
的所有字段都返回。
如果我們只想獲取其中的部分字段,我們可以添加_source
的過濾
直接指定字段
GET /heima/_search { "_source": ["title","price"], "query": { "term": { "price": 2699 } } } 返回的結果: { "took": 12, "timed_out": false, "_shards": { "total": 3, "successful": 3, "skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 1, "hits": [ { "_index": "heima", "_type": "goods", "_id": "r9c1KGMBIhaxtY5rlRKv", "_score": 1, "_source": { "price": 2699, "title": "小米手機" } } ] } }
指定includes和excludes
我們也可以通過:
- includes:來指定想要顯示的字段
- excludes:來指定不想要顯示的字段
二者都是可選的。
GET /heima/_search { "_source": { "includes":["title","price"] }, "query": { "term": { "price": 2699 } } } 與下面的結果將是一樣的: GET /heima/_search { "_source": { "excludes": ["images"] }, "query": { "term": { "price": 2699 } } }
高級查詢
布爾組合(bool)
bool
把各種其它查詢通過must
(與)、must_not
(非)、should
(或)的方式進行組合
GET /heima/_search { "query":{ "bool":{ "must": { "match": { "title": "大米" }}, "must_not": { "match": { "title": "電視" }}, "should": { "match": { "title": "手機" }} } } }
結果:
{ "took": 10, "timed_out": false, "_shards": { "total": 3, "successful": 3, "skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 0.5753642, "hits": [ { "_index": "heima", "_type": "goods", "_id": "2", "_score": 0.5753642, "_source": { "title": "大米手機", "images": "http://image.leyou.com/12479122.jpg", "price": 2899 } } ] } }
范圍查詢(range)
range
查詢找出那些落在指定區間內的數字或者時間
GET /heima/_search { "query":{ "range": { "price": { "gte": 1000.0, "lt": 2800.00 } } } }
range
查詢允許以下字符:
操作符 | 說明 |
---|---|
gt | 大於 |
gte | 大於等於 |
lt | 小於 |
lte | 小於等於 |
模糊查詢(fuzzy)
我們新增一個商品:
POST /heima/goods/4 { "title":"apple手機", "images":"http://image.leyou.com/12479122.jpg", "price":6899.00 }
fuzzy
查詢是 term
查詢的模糊等價。它允許用戶搜索詞條與實際詞條的拼寫出現偏差,但是偏差的編輯距離不得超過2:
GET /heima/_search { "query": { "fuzzy": { "title": "appla" } } }
上面的查詢,也能查詢到apple手機
我們可以通過fuzziness
來指定允許的編輯距離:
GET /heima/_search { "query": { "fuzzy": { "title": { "value":"appla", "fuzziness":1 } } } }
過濾(filter)
條件查詢中進行過濾
所有的查詢都會影響到文檔的評分及排名。如果我們需要在查詢結果中進行過濾,並且不希望過濾條件影響評分,那么就不要把過濾條件作為查詢條件來用。而是使用filter
方式:
GET /heima/_search { "query":{ "bool":{ "must":{ "match": { "title": "小米手機" }}, "filter":{ "range":{"price":{"gt":2000.00,"lt":3800.00}} } } } }
注意:filter
中還可以再次進行bool
組合條件過濾。
無查詢條件,直接過濾
如果一次查詢只有過濾,沒有查詢條件,不希望進行評分,我們可以使用constant_score
取代只有 filter 語句的 bool 查詢。在性能上是完全相同的,但對於提高查詢簡潔性和清晰度有很大幫助。
GET /heima/_search { "query":{ "constant_score": { "filter": { "range":{"price":{"gt":2000.00,"lt":3000.00}} } } }
排序
單字段排序
sort
可以讓我們按照不同的字段進行排序,並且通過order
指定排序的方式
GET /heima/_search { "query": { "match": { "title": "小米手機" } }, "sort": [ { "price": { "order": "desc" } } ] }
多字段排序
假定我們想要結合使用 price和 _score(得分) 進行查詢,並且匹配的結果首先按照價格排序,然后按照相關性得分排序:
GET /goods/_search { "query":{ "bool":{ "must":{ "match": { "title": "小米手機" }}, "filter":{ "range":{"price":{"gt":200000,"lt":300000}} } } }, "sort": [ { "price": { "order": "desc" }}, { "_score": { "order": "desc" }} ] }
聚合aggregations
聚合可以讓我們極其方便的實現對數據的統計、分析。例如:
- 什么品牌的手機最受歡迎?
- 這些手機的平均價格、最高價格、最低價格?
- 這些手機每月的銷售情況如何?
實現這些統計功能的比數據庫的sql要方便的多,而且查詢速度非常快,可以實現實時搜索效果。
基本概念
Elasticsearch中的聚合,包含多種類型,最常用的兩種,一個叫桶
,一個叫度量
:
桶(bucket)
桶的作用,是按照某種方式對數據進行分組,每一組數據在ES中稱為一個桶
,例如我們根據國籍對人划分,可以得到中國桶
、英國桶
,日本桶
……或者我們按照年齡段對人進行划分:0~10,10~20,20~30,30~40等。
Elasticsearch中提供的划分桶的方式有很多:
- Date Histogram Aggregation:根據日期階梯分組,例如給定階梯為周,會自動每周分為一組
- Histogram Aggregation:根據數值階梯分組,與日期類似
- Terms Aggregation:根據詞條內容分組,詞條內容完全匹配的為一組
- Range Aggregation:數值和日期的范圍分組,指定開始和結束,然后按段分組
- ……
綜上所述,我們發現bucket aggregations 只負責對數據進行分組,並不進行計算,因此往往bucket中往往會嵌套另一種聚合:metrics aggregations即度量
度量(metrics)
分組完成以后,我們一般會對組中的數據進行聚合運算,例如求平均值、最大、最小、求和等,這些在ES中稱為度量
比較常用的一些度量聚合方式:
- Avg Aggregation:求平均值
- Max Aggregation:求最大值
- Min Aggregation:求最小值
- Percentiles Aggregation:求百分比
- Stats Aggregation:同時返回avg、max、min、sum、count等
- Sum Aggregation:求和
- Top hits Aggregation:求前幾
- Value Count Aggregation:求總數
- ……
為了測試聚合,我們先批量導入一些數據
創建索引:
PUT /cars { "settings": { "number_of_shards": 1, "number_of_replicas": 0 }, "mappings": { "transactions": { "properties": { "color": { "type": "keyword" }, "make": { "type": "keyword" } } } } }
注意:在ES中,需要進行聚合、排序、過濾的字段其處理方式比較特殊,因此不能被分詞。這里我們將color和make這兩個文字類型的字段設置為keyword類型,這個類型不會被分詞,將來就可以參與聚合
導入數據
POST /cars/transactions/_bulk { "index": {}} { "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" } { "index": {}} { "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" } { "index": {}} { "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" } { "index": {}} { "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" } { "index": {}} { "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" } { "index": {}} { "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" } { "index": {}} { "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" } { "index": {}} { "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }
聚合為桶
首先,我們按照 汽車的顏色color
來划分桶
GET /cars/_search { "size" : 0, "aggs" : { "popular_colors" : { "terms" : { "field" : "color" } } } }
- size: 查詢條數,這里設置為0,因為我們不關心搜索到的數據,只關心聚合結果,提高效率
- aggs:聲明這是一個聚合查詢,是aggregations的縮寫
- popular_colors:給這次聚合起一個名字,任意。
- terms:划分桶的方式,這里是根據詞條划分
- field:划分桶的字段
結果:
{ "took": 1, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": 8, "max_score": 0, "hits": [] }, "aggregations": { "popular_colors": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "red", "doc_count": 4 }, { "key": "blue", "doc_count": 2 }, { "key": "green", "doc_count": 2 } ] } } }
- hits:查詢結果為空,因為我們設置了size為0
- aggregations:聚合的結果
- popular_colors:我們定義的聚合名稱
- buckets:查找到的桶,每個不同的color字段值都會形成一個桶
- key:這個桶對應的color字段的值
- doc_count:這個桶中的文檔數量
通過聚合的結果我們發現,目前紅色的小車比較暢銷!
桶內度量
前面的例子告訴我們每個桶里面的文檔數量,這很有用。 但通常,我們的應用需要提供更復雜的文檔度量。 例如,每種顏色汽車的平均價格是多少?
因此,我們需要告訴Elasticsearch使用哪個字段
,使用何種度量方式
進行運算,這些信息要嵌套在桶
內,度量
的運算會基於桶
內的文檔進行
現在,我們為剛剛的聚合結果添加 求價格平均值的度量:
GET /cars/_search { "size" : 0, "aggs" : { "popular_colors" : { "terms" : { "field" : "color" }, "aggs":{ "avg_price": { "avg": { "field": "price" } } } } } }
- aggs:我們在上一個aggs(popular_colors)中添加新的aggs。可見
度量
也是一個聚合,度量是在桶內的聚合 - avg_price:聚合的名稱
- avg:度量的類型,這里是求平均值
- field:度量運算的字段
結果:
... "aggregations": { "popular_colors": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "red", "doc_count": 4, "avg_price": { "value": 32500 } }, { "key": "blue", "doc_count": 2, "avg_price": { "value": 20000 } }, { "key": "green", "doc_count": 2, "avg_price": { "value": 21000 } } ] } } ...
可以看到每個桶中都有自己的avg_price
字段,這是度量聚合的結果
桶內嵌套桶
剛剛的案例中,我們在桶內嵌套度量運算。事實上桶不僅可以嵌套運算, 還可以再嵌套其它桶。也就是說在每個分組中,再分更多組。
比如:我們想統計每種顏色的汽車中,分別屬於哪個制造商,按照make
字段再進行分桶
GET /cars/_search { "size" : 0, "aggs" : { "popular_colors" : { "terms" : { "field" : "color" }, "aggs":{ "avg_price": { "avg": { "field": "price" } }, "maker":{ "terms":{ "field":"make" } } } } } }
- 原來的color桶和avg計算我們不變
- maker:在嵌套的aggs下新添一個桶,叫做maker
- terms:桶的划分類型依然是詞條
- filed:這里根據make字段進行划分
部分結果:
... {"aggregations": { "popular_colors": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "red", "doc_count": 4, "maker": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "honda", "doc_count": 3 }, { "key": "bmw", "doc_count": 1 } ] }, "avg_price": { "value": 32500 } }, { "key": "blue", "doc_count": 2, "maker": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "ford", "doc_count": 1 }, { "key": "toyota", "doc_count": 1 } ] }, "avg_price": { "value": 20000 } }, { "key": "green", "doc_count": 2, "maker": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "ford", "doc_count": 1 }, { "key": "toyota", "doc_count": 1 } ] }, "avg_price": { "value": 21000 } } ] } } } ...
- 我們可以看到,新的聚合
maker
被嵌套在原來每一個color
的桶中。 - 每個顏色下面都根據
make
字段進行了分組 - 我們能讀取到的信息:
- 紅色車共有4輛
- 紅色車的平均售價是 $32,500 美元。
- 其中3輛是 Honda 本田制造,1輛是 BMW 寶馬制造
划分桶的其它方式
前面講了,划分桶的方式有很多,例如:
- Date Histogram Aggregation:根據日期階梯分組,例如給定階梯為周,會自動每周分為一組
- Histogram Aggregation:根據數值階梯分組,與日期類似
- Terms Aggregation:根據詞條內容分組,詞條內容完全匹配的為一組
- Range Aggregation:數值和日期的范圍分組,指定開始和結束,然后按段分組
剛剛的案例中,我們采用的是Terms Aggregation,即根據詞條划分桶。
接下來,我們再學習幾個比較實用的:
階梯分桶Histogram
原理: histogram是把數值類型的字段,按照一定的階梯大小進行分組。你需要指定一個階梯值(interval)來划分階梯大小。
舉例:比如你有價格字段,如果你設定interval的值為200,那么階梯就會是這樣的:
0,200,400,600,...
上面列出的是每個階梯的key,也是區間的啟點。
如果一件商品的價格是450,會落入哪個階梯區間呢?計算公式如下:
bucket_key = Math.floor((value - offset) / interval) * interval + offset
value:就是當前數據的值,本例中是450
offset:起始偏移量,默認為0
interval:階梯間隔,比如200
因此你得到的key = Math.floor((450 - 0) / 200) * 200 + 0 = 400
操作一下:
比如,我們對汽車的價格進行分組,指定間隔interval為5000:
GET /cars/_search { "size":0, "aggs":{ "price":{ "histogram": { "field": "price", "interval": 5000 } } } }
結果:
{ "took": 21, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 8, "max_score": 0, "hits": [] }, "aggregations": { "price": { "buckets": [ { "key": 10000, "doc_count": 2 }, { "key": 15000, "doc_count": 1 }, { "key": 20000, "doc_count": 2 }, { "key": 25000, "doc_count": 1 }, { "key": 30000, "doc_count": 1 }, { "key": 35000, "doc_count": 0 }, { "key": 40000, "doc_count": 0 }, { "key": 45000, "doc_count": 0 }, { "key": 50000, "doc_count": 0 }, { "key": 55000, "doc_count": 0 }, { "key": 60000, "doc_count": 0 }, { "key": 65000, "doc_count": 0 }, { "key": 70000, "doc_count": 0 }, { "key": 75000, "doc_count": 0 }, { "key": 80000, "doc_count": 1 } ] } } }
你會發現,中間有大量的文檔數量為0 的桶,看起來很丑。
我們可以增加一個參數min_doc_count為1,來約束最少文檔數量為1,這樣文檔數量為0的桶會被過濾
示例:
GET /cars/_search { "size":0, "aggs":{ "price":{ "histogram": { "field": "price", "interval": 5000, "min_doc_count": 1 } } } }
結果:
{ "took": 15, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 8, "max_score": 0, "hits": [] }, "aggregations": { "price": { "buckets": [ { "key": 10000, "doc_count": 2 }, { "key": 15000, "doc_count": 1 }, { "key": 20000, "doc_count": 2 }, { "key": 25000, "doc_count": 1 }, { "key": 30000, "doc_count": 1 }, { "key": 80000, "doc_count": 1 } ] } } }
完美,!
如果你用kibana將結果變為柱形圖,會更好看:
范圍分桶range
范圍分桶與階梯分桶類似,也是把數字按照階段進行分組,只不過range方式需要你自己指定每一組的起始和結束大小。