前言
經過前兩篇文章得實踐,我們已經了解了ElasticSearch的基礎知識,本篇文章讓我來操作一些更真實的數據集。
我們可以利用www.json-generator.com/生成如下的文檔結構:
{
"account_number": 1,
"balance": 39225,
"firstname": "Amber",
"lastname": "Duke",
"age": 32,
"gender": "M",
"address": "880 Holmes Lane",
"employer": "Pyrami",
"email": "amberduke@pyrami.com",
"city": "Brogan",
"state": "IL"
}
加載簡單數據集
我們可以下載es提供的數據集accounts.json,然后推送到ES集群
wget https://github.com/elastic/elasticsearch/blob/master/docs/src/test/resources/accounts.json
curl -H "Content-Type: application/json" -XPOST "localhost:9200/bank/_doc/_bulk?pretty&refresh" --data-binary "@accounts.json"
curl "localhost:9200/_cat/indices?v"
我們可以看到1000個文檔已經索引到bank索引下了。
[root@XXXXX cusD]# curl "localhost:9200/_cat/indices?v"
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open index 3BGZ895tTNa8qtM_nA3YmA 5 1 1 0 4.4kb 4.4kb
green open .kibana qCbYeswVT2WCogz_E9Y3Ag 1 0 2 0 13.7kb 13.7kb
yellow open customer x57uWBR3Rg-w2_Dz7Djduw 5 1 1 0 4.5kb 4.5kb
yellow open customerb 80DoY8e3RtinVNV4VGU4Cg 5 1 1 0 4.5kb 4.5kb
yellow open best3 DPh-_bOLQBimS9jqWVyyjw 5 1 3 0 10.9kb 10.9kb
yellow open best1 oD5uUlCbSnqevbRfLvl2Iw 5 1 1 0 5.5kb 5.5kb
yellow open customer2 VyIXSBK6R9yHNYNDlsni3A 5 1 0 0 1.2kb 1.2kb
yellow open customerc Nbglz5hbRO28jyt_XyPNTA 5 1 1 0 4.5kb 4.5kb
yellow open cust xuYth97RShixNtgNpbyxBA 5 1 1 0 4.4kb 4.4kb
yellow open customerf osKgtSLxTPKblJW7mrmO0Q 5 1 1 0 5.1kb 5.1kb
yellow open bank Wrk49iM6TjGItiZKWdnzJA 5 1 1000 0 474.7kb 474.7kb
yellow open customer3 101ZzeNmRuCn9d_NOx5oZg 5 1 0 0 1.2kb 1.2kb
yellow open customere p2BWLci9Qz-1VnOh0vSSQA 5 1 2 0 7.6kb 7.6kb
搜索API
讓我們開始運行一些簡單的搜索api,有兩種方式:
- 發(fā)送參數到REST request URI
- 發(fā)送請求到REST request body,請求body支持json格式,易讀易使用。
GET /bank/_search?q=*&sort=account_number:asc&pretty
讓我們分析下這個搜索請求。我們正在用_search搜索 bank索引。q=代表Es會匹配索引內的全部文檔。sort=account_number:asc代表每個文檔的字段以account_number升序對結果進行排序。pretty*代表結果以漂亮的json格式輸出。
這里摘選部分結果
{
"took": 53,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1000,
"max_score": null,
"hits": [
{
"_index": "bank",
"_type": "_doc",
"_id": "0",
"_score": null,
"_source": {
"account_number": 0,
"balance": 16623,
"firstname": "Bradshaw",
"lastname": "Mckenzie",
"age": 29,
"gender": "F",
"address": "244 Columbus Place",
"employer": "Euron",
"email": "bradshawmckenzie@euron.com",
"city": "Hobucken",
"state": "CO"
},
"sort": [
0
]
},
}
- took - Elasticsearch執(zhí)行搜索的時間(以毫秒為單位)
- timed_out - 告訴我們搜索是否超時
- _shards - 告訴我們搜索了多少個分片,以及搜索成功/失敗分片的計數
- hits - 搜索結果
- hits.total - 符合我們搜索條件的文檔總數
- hits.hits - 實際的搜索結果數組(默認為前10個文檔)
- hits.sort - 對結果進行排序(如果按分數排序則丟失)
- hits._score并max_score- 暫時忽略這些字段
也可以用Request Body方式執(zhí)行搜索,格式如下:
GET /bank/_search
{
"query": { "match_all": {} },
"sort": [
{ "account_number": "asc" }
]
}
介紹查詢語言【Query Language】
Elasticsearch提供了一種JSON樣式的特定于域的語言,可用于執(zhí)行查詢。這被稱為查詢DSL。查詢語言非常全面,乍一看可能令人生畏,但實際學習它的最佳方法是從一些基本示例開始。
回到上面的例子,我們執(zhí)行查詢:
GET /bank/_search
{
"query": { "match_all": {} }
}
解析上面的內容,該query部分告訴我們查詢定義是什么,match_all部分只是我們想要運行的查詢類型。該match_all查詢僅僅是在指定索引的所有文件進行搜索。
除了query參數,我們還可以傳遞其他參數來影響搜索結果。在上面我們傳入的部分的示例中 sort,我們傳入size:
GET /bank/_search
{
"query": { "match_all": {} },
"size": 1
}
請注意,如果size未指定,則默認為10。
此示例執(zhí)行一個 match_all并返回文檔10到19:
GET /bank/_search
{
"query": { "match_all": {} },
"from": 10,
"size": 10
}
from規(guī)定文檔開始的索引,size指定了查詢文檔的大小。在實現分頁時,這兩個參數非常有用。from如果不傳,默認為0。
下面的示例執(zhí)行一個 match_all并按帳戶余額降序對結果進行排序,返回前10個(默認大?。┪臋n。
GET /bank/_search
{
"query": { "match_all": {} },
"sort": { "balance": { "order": "desc" } }
}
執(zhí)行搜索
上面我們已經看到了一些基本的查詢示例,讓我們再深入了解下QueryDSL。讓我們來看下返回的json文檔的字段。默認情況下會返回命中文檔的所有字段。這被稱為源(_source代表命中的字段)。有些情況下,我們只需要部分字段,如下:
GET /bank/_search
{
"query": { "match_all": {} },
"_source": ["account_number", "balance"]
}
下面我們來說說查詢部分。之前我們講過match_all是匹配所有文檔,現在讓我們了解一個match query,它能針對特定字段或字段集進行搜索。
下面這個示例能搜索account_number為20的數據:
GET /bank/_search
{
"query": { "match": { "account_number": 20 } }
}
此示例返回地址中包含術語“mill”或“l(fā)ane”的所有帳戶,這里格外注意【空格隔開的兩個單詞是or查詢】:
GET /bank/_search
{
"query": { "match": { "address": "mill lane" } }
}
此示例演示地址種包含“mill lane”的所有賬戶,【用match_phrase查詢時,空格隔開的依然是一個單詞】
GET /bank/_search
{
"query": { "match_phrase": { "address": "mill lane" } }
}
然后我們繼續(xù)介紹下 bool query,它允許我們使用布爾查詢將更小的查詢組合成更大的查詢。
must 同時滿足條件此示例組成兩個match查詢并返回地址中包含“mill”和“l(fā)ane”的所有帳戶:
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
在上面的示例中,該bool must子句指定必須為true才能將文檔視為匹配的所有查詢。
should或滿足一個即可 此示例組成兩個match查詢并返回地址中包含“mill”或“l(fā)ane”的所有帳戶:
GET /bank/_search
{
"query": {
"bool": {
"should": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
在上面的示例中,該bool should子句指定了一個查詢列表,其中任何一個查詢都必須為true,才能將文檔視為匹配項。
must_not都不包含 此示例組成兩個match查詢并返回地址中既不包含“mill”也不包含“l(fā)ane”的所有帳戶:
GET /bank/_search
{
"query": {
"bool": {
"must_not": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
我們可以在查詢中同時組合must,should和must_not子句bool。此外,我們可以bool在任何這些bool子句中組合查詢來模仿任何復雜的多級布爾邏輯。
此示例返回任何40歲但不住在ID(aho)的人的所有帳戶
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "age": "40" } }
],
"must_not": [
{ "match": { "state": "ID" } }
]
}
}
}
執(zhí)行過濾器
上面的示例中,我們跳過了一個稱為文檔分數的小細節(jié)(_score搜索結果中的字段)。分數是一個數值,它是文檔與我們指定的搜索查詢匹配程度的相對度量。分數越高,文檔越相關,分數越低,文檔的相關性越低。
但是查詢并不總是需要產生分數,特別是當它們僅用于“過濾”文檔集時。Elasticsearch會檢測這些情況并自動優(yōu)化查詢執(zhí)行,以便不計算無用的分數。
我們在上面示例介紹的bool查詢還支持filter允許使用查詢來限制將與其他子句匹配的文檔的子句,而不會更改計算得分的方式。作為示例,讓我們介紹一下range查詢,它允許我們按一系列值過濾文檔。一般數字或日期會用到range。
此示例使用bool查詢返回余額大于或等于20000且小于或等于30000的帳戶。
GET /bank/_search
{
"query": {
"bool": {
"must": { "match_all": {} },
"filter": {
"range": {
"balance": {
"gte": 20000,
"lte": 30000
}
}
}
}
}
}
解析上面的內容,bool查詢包含match_all查詢(查詢部分)和range查詢(過濾部分)。我們可以將任何其他查詢替換為查詢和過濾器部分。
除了match_all,match,bool,和range查詢,有很多可用的其他查詢類型的,這里暫時不講了,我們了解了大致的工作原理后,將這些知識應用于學習和試驗其他查詢類型應該不會太困難。
執(zhí)行聚合
聚合提供了從數據中分組和提取統(tǒng)計信息的功能??紤]聚合的最簡單方法是將其大致等同于SQL GROUP BY和SQL聚合函數。在Elasticsearch中,您可以執(zhí)行返回匹配的搜索,同時在一個響應中返回與命中相關的聚合結果。這是非常強大和高效的,因為您可以運行查詢和多個聚合,并一次性獲取兩個(或任一)操作的結果,避免使用簡潔和簡化的API進行網絡往返。
首先,此示例按state對所有帳戶進行分組,然后返回按計數降序排序的前10個(默認)states(也是默認值),(這里的group_by_state可理解成自定義的聚合名稱,可以自定義改變):
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword"
}
}
}
}
在SQL中,上面的聚合類似:
SELECT state, COUNT() FROM bank GROUP BY state ORDER BY COUNT() DESC LIMIT 10;
返回結果如下。
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1000,
"max_score": 0,
"hits": []
},
"aggregations": {
"group_by_state": {
"doc_count_error_upper_bound": 20,
"sum_other_doc_count": 770,
"buckets": [
{
"key": "ID",
"doc_count": 27
},
{
"key": "TX",
"doc_count": 27
},
{
"key": "AL",
"doc_count": 25
},
{
"key": "MD",
"doc_count": 25
},
{
"key": "TN",
"doc_count": 23
},
{
"key": "MA",
"doc_count": 21
},
{
"key": "NC",
"doc_count": 21
},
{
"key": "ND",
"doc_count": 21
},
{
"key": "ME",
"doc_count": 20
},
{
"key": "MO",
"doc_count": 20
}
]
}
}
}
我們可以看到key為ID的有27個賬戶,TX也是27個賬戶,AL的是25個賬戶,以此類推。
請注意,我們設置size=0為不顯示搜索匹配,因為我們只希望在響應中看到聚合結果。
在前一個聚合的基礎上,此示例按州計算平均帳戶余額(同樣僅針對按降序排序的前10個州):
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
請注意我們如何嵌套average_balance聚合內的group_by_state聚合。這是所有聚合的常見模式。您可以在聚合中任意嵌套聚合,以從數據中提取所需的輪轉摘要。
在前一個聚合的基礎上,我們現在按降序排列平均余額:
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword",
"order": {
"average_balance": "desc"
}
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
此示例演示了我們如何按年齡段(20-29歲,30-39歲和40-49歲)進行分組,然后按性別分組,最后得到每個年齡段的平均帳戶余額:
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_age": {
"range": {
"field": "age",
"ranges": [
{
"from": 20,
"to": 30
},
{
"from": 30,
"to": 40
},
{
"from": 40,
"to": 50
}
]
},
"aggs": {
"group_by_gender": {
"terms": {
"field": "gender.keyword"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
}
}
還有許多其他聚合功能,我們在此不再詳述。如果您想進行進一步的實驗,聚合參考指南是一個很好的起點。
總結
本篇文章依據官方文檔,實踐了查詢和聚合命令,前面查詢的部分還是很簡單的,聚合這塊有些復雜。
本篇到此結束,感謝觀看。有興趣的可以通過 http://www.weixinhe.cn:5601 演示上述命令。