【轉】ES的常用查詢與聚合
原文地址:http://blog.51cto.com/xpleaf/2307572
0 說明
基於es 5.4和es 5.6,列舉的是個人工作中經常用到的查詢(只是工作中使用的是Java API),如果需要看完整的,可以參考官方相關文件
https://www.elastic.co/guide/en/elasticsearch/reference/5.4/search.html。
1 查詢
先使用一個快速入門來引入,然後後面列出的各種查詢都是用得比較多的(在我的工作環境是這樣),其它沒怎麼用的這裡就不列出了。
1.1 快速入門
1.1.1 查詢全部
GET index/type/_search
{
"query":{
"match_all":{}
}
}
或
GET index/type/_search
1.1.2 分頁(以term為例)
GET index/type/_search
{
"from":0,
"size":100,
"query":{ "term":{ "area":"GuangZhou" } } }
1.1.3 包含指定欄位(以term為例)
GET index/type/_search
{
"_source":["hobby", "name"],
"query":{
"term":{ "area":"GuangZhou" } } }
1.1.4 排序(以term為例)
單個欄位排序:
GET index/type/_search
{
"query":{
"term":{
"area":"GuangZhou"
}
},
"sort":[ {"user_id":{"order":"asc"}}, {"salary":{"order":"desc"}} ] }
1.2 全文查詢
查詢欄位會被索引和分析,在執行之前將每個欄位的分詞器(或搜尋分詞器)應用於查詢字串。
1.2.1 match query
{
"query": {
"match": {
"content": {
"query": "裡皮恆大", "operator": "and" } } } }
operator預設是or,也就是說,“裡皮恆大”被分詞為“裡皮”和“恆大”,只要content中出現兩個之一,都會搜尋到;設定為and之後,只有同時出現都會被搜尋到。
1.2.2 match_phrase query
文件同時滿足下面兩個條件才會被搜尋到:
- (1)分詞後所有詞項都要出現在該欄位中
- (2)欄位中的詞項順序要一致
{
"query": {
"match_phrase": {
"content": "裡皮恆大"
}
}
}
1.3 詞項查詢
詞項搜尋時對倒排索引中儲存的詞項進行精確匹配,詞項級別的查詢通過用於結構化資料,如數字、日期和列舉型別。
1.3.1 term query
{
"query": {
"term": {
"postdate": "2015-12-10 00:41:00"
}
}
}
1.3.2 terms query
term的升級版,如上面查詢的postdate欄位,可以設定多個。
{
"query": {
"terms": {
"postdate": [
"2015-12-10 00:41:00",
"2016-02-01 01:39:00" ] } } }
因為term是精確匹配,所以不要問,[]中的關係怎麼設定and?這怎麼可能,既然是精確匹配,一個欄位也不可能有兩個不同的值。
1.3.3 range query
匹配某一範圍內的資料型、日期型別或者字串型欄位的文件,注意只能查詢一個欄位,不能作用在多個欄位上。
數值:
{
"query": {
"range": {
"reply": {
"gte": 245, "lte": 250 } } } }
支援的操作符如下:
gt:大於,gte:大於等於,lt:小於,lte:小於等於
日期:
{
"query": {
"range": {
"postdate": {
"gte": "2016-09-01 00:00:00", "lte": "2016-09-30 23:59:59", "format": "yyyy-MM-dd HH:mm:ss" } } } }
format不加也行,如果寫的時間格式正確。
1.3.4 exists query
返回對應欄位中至少有一個非空值的文件,也就是說,該欄位有值(待會會說明這個概念)。
{
"query": {
"exists": {
"field": "user"
}
}
}
參考《從Lucene到Elasticsearch:全文檢索實戰》中的說明。
以下文件會匹配上面的查詢:
文件 | 說明 |
---|---|
{"user":"jane"} | 有user欄位,且不為空 |
{"user":""} | 有user欄位,值為空字串 |
{"user":"-"} | 有user欄位,值不為空 |
{"user":["jane"]} | 有user欄位,值不為空 |
{"user":["jane",null]} | 有user欄位,至少一個值不為空即可 |
下面的文件不會被匹配:
文件 | 說明 |
---|---|
{"user":null} | 雖然有user欄位,但是值為空 |
{"user":[]} | 雖然有user欄位,但是值為空 |
{"user":[null]} | 雖然有user欄位,但是值為空 |
{"foo":"bar"} | 沒有user欄位 |
1.3.5 ids query
查詢具有指定id的文件。
{
"query": {
"ids": {
"type": "news",
"values": "2101" } } }
型別是可選的,也可以以資料的方式指定多個id。
{
"query": {
"ids": {
"values": [
"2101",
"2301" ] } } }
1.4 複合查詢
1.4.1 bool query
因為工作中接觸到關於es是做聚合、統計、分類的專案,經常要做各種複雜的多條件查詢,所以實際上,bool query用得非常多,因為查詢條件個數不定,所以處理的邏輯思路時,外層用一個大的bool query來進行承載。(當然,專案中是使用其Java API)
bool query可以組合任意多個簡單查詢,各個簡單查詢之間的邏輯表示如下:
屬性 | 說明 |
---|---|
must | 文件必須匹配must選項下的查詢條件,相當於邏輯運算的AND |
should | 文件可以匹配should選項下的查詢條件,也可以不匹配,相當於邏輯運算的OR |
must_not | 與must相反,匹配該選項下的查詢條件的文件不會被返回 |
filter | 和must一樣,匹配filter選項下的查詢條件的文件才會被返回,但是filter不評分,只起到過濾功能 |
一個例子如下:
{
"query": {
"bool": {
"must": {
"match": {
"content": "裡皮" } }, "must_not": { "match": { "content": "中超" } } } } }
需要注意的是,同一個bool下,只能有一個must、must_not、should和filter。
如果希望有多個must時,比如希望同時匹配"裡皮"和"中超",但是又故意分開這兩個關鍵詞(因為事實上,一個must,然後使用match,並且operator為and就可以達到目的),怎麼操作?注意must下使用陣列,然後裡面多個match物件就可以了:
{
"size": 1,
"query": {
"bool": {
"must": [ { "match": { "content": "裡皮" } }, { "match": { "content": "恆大" } } ] } }, "sort": [ { "id": { "order": "desc" } } ] }
當然must下的陣列也可以是多個bool查詢條件,以進行更加複雜的查詢。
上面的查詢等價於:
{
"query": {
"bool": {
"must": {
"match": {
"content": { "query": "裡皮恆大", "operator": "and" } } } } }, "sort": [ { "id": { "order": "desc" } } ] }
1.5 巢狀查詢
先新增下面一個索引:
PUT /my_index
{
"mappings": {
"my_type": {
"properties": {
"user":{
"type": "nested", "properties": { "first":{"type":"keyword"}, "last":{"type":"keyword"} } }, "group":{ "type": "keyword" } } } } }
新增資料:
PUT my_index/my_type/1
{
"group":"GuangZhou",
"user":[
{
"first":"John", "last":"Smith" }, { "first":"Alice", "last":"White" } ] } PUT my_index/my_type/2 { "group":"QingYuan", "user":[ { "first":"Li", "last":"Wang" }, { "first":"Yonghao", "last":"Ye" } ] }
查詢:
較簡單的查詢:
{
"query": {
"nested": {
"path": "user",
"query": { "term": { "user.first": "John" } } } } }
較複雜的查詢:
{
"query": {
"bool": {
"must": [
{"nested": {
"path": "user", "query": { "term": { "user.first": { "value": "Li" } } } }}, { "nested": { "path": "user", "query": { "term": { "user.last": { "value": "Wang" } } } } } ] } } }
1.6 補充:陣列查詢與測試
新增一個索引:
PUT my_index2
{
"mappings": {
"my_type2":{
"properties": {
"message":{ "type": "text" }, "keywords":{ "type": "keyword" } } } } }
新增資料:
PUT /my_index2/my_type/1
{
"message":"keywords test1",
"keywords":["美女","動漫","電影"] } PUT /my_index2/my_type/2 { "message":"keywords test2", "keywords":["電影","美妝","廣告"] }
搜尋:
{
"query": {
"term": {
"keywords": "廣告"
}
}
}
Note1:注意設定欄位型別時,keywords設定為keyword,所以使用term查詢可以精確匹配,但設定為text,則不一定——如果有新增分詞器,則可以搜尋到;如果沒有,而是使用預設的分詞器,只是將其分為一個一個的字,就不會被搜尋到。這點尤其需要注意到。
Note2:對於陣列欄位,也是可以做桶聚合的,做桶聚合的時候,其每一個值都會作為一個值去進行分組,而不是整個陣列進行分組,可以使用上面的進行測試,不過需要注意的是,其欄位型別不能為text,否則聚合會失敗。
Note3:所以根據上面的提示,一般純陣列比較適合存放標籤類的資料,就像上面的案例一樣,同時欄位型別設定為keyword,而不是text,搜尋時進行精確匹配就好了。
1.7 滾動查詢scroll
如果一次性要查出來比如10萬條資料,那麼效能會很差,此時一般會採取用scoll滾動查詢,一批一批的查,直到所有資料都查詢完處理完(es返回的scrollId,可以理解為是es進行此次查詢的操作控制代碼標識,每傳送一次該scrollId,es都會操作一次,或者說迴圈一次,直到時間視窗到期)。
使用scoll滾動搜尋,可以先搜尋一批資料,然後下次再搜尋一批資料,以此類推,直到搜尋出全部的資料來,scoll搜尋會在第一次搜尋的時候,儲存一個當時的檢視快照,之後只會基於該舊的檢視快照提供資料搜尋,如果這個期間資料變更,是不會讓使用者看到的,每次傳送scroll請求,我們還需要指定一個scoll引數,指定一個時間視窗,每次搜尋請求只要在這個時間視窗內能完成就可以了(也就是說,該scrollId只在這個時間視窗內有效,檢視快照也是)。
GET spnews/news/_search?scroll=1m
{
"query": {
"match_all": {}
},
"size": 10,
"_source": ["id"] } GET _search/scroll { "scroll":"1m", "scroll_id":"DnF1ZXJ5VGhlbkZldGNoAwAAAAAAADShFmpBMjJJY2F2U242RFU5UlAzUzA4MWcAAAAAAAA0oBZqQTIySWNhdlNuNkRVOVJQM1MwODFnAAAAAAAANJ8WakEyMkljYXZTbjZEVTlSUDNTMDgxZw==" }
2 聚合
2.1 指標聚合
相當於MySQL的聚合函式。
max
{
"size": 0,
"aggs": {
"max_id": {
"max": { "field": "id" } } } }
size不設定為0,除了返回聚合結果外,還會返回其它所有的資料。
min
{
"size": 0,
"aggs": {
"min_id": {
"min": { "field": "id" } } } }
avg
{
"size": 0,
"aggs": {
"avg_id": {
"avg": { "field": "id" } } } }
sum
{
"size": 0,
"aggs": {
"sum_id": {
"sum": { "field": "id" } } } }
stats
{
"size": 0,
"aggs": {
"stats_id": {
"stats": { "field": "id" } } } }
2.2 桶聚合
相當於MySQL的group by操作,所以不要嘗試對es中text的欄位進行桶聚合,否則會失敗。
Terms
相當於分組查詢,根據欄位做聚合。
{
"size": 0,
"aggs": {
"per_count": {
"terms": { "size":100, "field": "vtype", "min_doc_count":1 } } } }
在桶聚合的過程中還可以進行指標聚合,相當於mysql做group by之後,再做各種max、min、avg、sum、stats之類的:
{
"size": 0,
"aggs": {
"per_count": {
"terms": { "field": "vtype" }, "aggs": { "stats_follower": { "stats": { "field": "realFollowerCount" } } } } } }
Filter
相當於是MySQL根據where條件過濾出結果,然後再做各種max、min、avg、sum、stats操作。
{
"size": 0,
"aggs": {
"gender_1_follower": {
"filter": { "term": { "gender": 1 } }, "aggs": { "stats_follower": { "stats": { "field": "realFollowerCount" } } } } } }
上面的聚合操作相當於是:查詢gender為1的各個指標。
Filters
在Filter的基礎上,可以查詢多個欄位各自獨立的各個指標,即對每個查詢結果分別做指標聚合。
{
"size": 0,
"aggs": {
"gender_1_2_follower": {
"filters": { "filters": [ { "term": { "gender": 1 } }, { "term": { "gender": 2 } } ] }, "aggs": { "stats_follower": { "stats": { "field": "realFollowerCount" } } } } } }
Range
{
"size": 0,
"aggs": {
"follower_ranges": {
"range": { "field": "realFollowerCount", "ranges": [ { "to": 500 }, { "from": 500, "to": 1000 }, { "from": 1000, "to": 1500 }, { "from": "1500", "to": 2000 }, { "from": 2000 } ] } } } }
to:小於,from:大於等於
Date Range
跟上面一個類似的,其實只是欄位為日期型別的,然後範圍值也是日期。
Date Histogram Aggregation
這個功能十分有用,可以根據年月日來對資料進行分類。
索引下面的文件:
DELETE my_blog
PUT my_blog
{
"mappings": {
"article":{
"properties": {
"title":{"type": "text"}, "postdate":{ "type": "date" , "format": "yyyy-MM-dd HH:mm:ss" } } } } } PUT my_blog/article/1 { "title":"Elasticsearch in Action", "postdate":"2014-09-23 23:34:12" } PUT my_blog/article/2 { "title":"Spark in Action", "postdate":"2015-09-13 14:12:22" } PUT my_blog/article/3 { "title":"Hadoop in Action", "postdate":"2016-08-23 23:12:22" }
按年對資料進行聚合:
GET my_blog/article/_search
{
"size": 0,
"aggs": {
"agg_year": {
"date_histogram": {
"field": "postdate", "interval": "year", "order": { "_key": "asc" } } } } } { "took": 18, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 3, "max_score": 0, "hits": [] }, "aggregations": { "agg_year": { "buckets": [ { "key_as_string": "2014-01-01 00:00:00", "key": 1388534400000, "doc_count": 1 }, { "key_as_string": "2015-01-01 00:00:00", "key": 1420070400000, "doc_count": 1 }, { "key_as_string": "2016-01-01 00:00:00", "key": 1451606400000, "doc_count": 1 } ] } } }
按月對資料進行聚合:
GET my_blog/article/_search
{
"size": 0,
"aggs": {
"agg_year": {
"date_histogram": {
"field": "postdate", "interval": "month", "order": { "_key": "asc" } } } } }
這樣聚合的話,包含的年份的每一個月的資料都會被分類,不管其是否包含文件。
按日對資料進行聚合:
GET my_blog/article/_search
{
"size": 0,
"aggs": {
"agg_year": {
"date_histogram": {
"field": "postdate", "interval": "day", "order": { "_key": "asc" } } } } }
這樣聚合的話,包含的年份的每一個月的每一天的資料都會被分類,不管其是否包含文件。