原文:https://www.fanhaobai.com/2017/08/elasticsearch-advanced-search.htmlhtml
文章 Elasticsearch檢索實戰 已經講述了 Elasticsearch 基本檢索使用,已知足大部分檢索場景,可是某些特定項目中會使用到 聚合 和 LBS 這類高級檢索,以知足檢索需求。這裏將講述 Elasticsearch 的聚合和 LBS 檢索使用方法。數組
本文示例的房源數據,見這裏,檢索一樣使用 Elasticsearch 的 DSL 對比 SQL 來講明。app
aggs 子句聚合是 Elasticsearch 常規的聚合實現方式。elasticsearch
先理解這兩個基本概念:學習
名稱 | 描述 |
---|---|
桶(Buckets) | 知足特定條件的文檔的集合 |
指標(Metrics) | 對桶內的文檔進行統計計算 |
每一個聚合都是 一個或者多個桶和零個或者多個指標 的組合,聚合可能只有一個桶,可能只有一個指標,或者可能兩個都有。例如這個 SQL:rest
SELECT COUNT(field_name) FROM table GROUP BY field_name
其中COUNT(field_name)
至關於指標,GROUP BY field_name
至關於桶。桶在概念上相似於 SQL 的分組(GROUP BY),而指標則相似於 COUNT() 、 SUM() 、 MAX() 等統計方法。code
桶和指標的可用取值列表:htm
分類 | 操做符 | 描述 |
---|---|---|
桶 | terms | 按精確值劃分桶 |
指標 | sum | 桶內對該字段值求總數 |
指標 | min | 桶內對該字段值求最小值 |
指標 | max | 桶內對該字段值求最大值 |
指標 | avg | 桶內對該字段值求平均數 |
指標 | cardinality( 基數) | 桶內對該字段不一樣值的數量(distinct 值) |
Elasticsearch 聚合 DSL 描述以下:對象
"aggs" : { "aggs_name" : { "operate" : { "field" : "field_name" } } }
其中,aggs_name 表示聚合結果返回的字段名,operate 表示桶或指標的操做符名,field_name 爲須要進行聚合的字段。排序
-- SQL描述 SELECT resblockId, COUNT(resblockId) FROM rooms WHERE bizcircleCode = 611100314 GROUP BY resblockId
Elasticsearch 聚合爲:
{ "query": { "constant_score": { "filter": { "bool": { "must": [{ "term": { "bizcircleCode": 611100314 }}] } } } }, "aggs": { "resblock_list": { "terms": { "field": "resblockId" } } } }
聚合結果以下:
{ "hits": { "total": 6, "max_score": 1, "hits": [... ...] }, "aggregations": { "resblock_list": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": 1321052240532, //小區id爲1321052240532有4間房 "doc_count": 4 }, { "key": 1111047349969,//小區id爲1111047349969有1間房 "doc_count": 1 }, { "key": 1111050770108,//小區id爲1111050770108有1間房 "doc_count": 1 } ] } }}
可見,此時聚合的結果有且只有分組後文檔的 數量,只適合作一些分組後文檔數的統計。
-- SQL描述 SELECT COUNT(DISTINCT resblockId) FROM rooms WHERE bizcircleCode = 611100314
使用 cardinality 指標統計:
{ "aggs": { "resblock_count": { "cardinality": { "field": "resblockId" } } } }
上述的簡單聚合,雖然能夠統計桶內的文檔數量,可是無法實現組內的其餘指標統計,好比小區內的最低房源價格,這時就能夠給桶添加一個 min 指標。
-- SQL描述 SELECT resblockId, MIN(price) FROM rooms WHERE bizcircleCode = 611100314
添加 min 指標後爲:
{ "aggs": { "resblock_list": { "terms": { "field": "resblockId" }, "aggs": { "min_price": { "min": { "field": "price" } } } } } }
結果爲:
"buckets": [ { "key": 1321052240532, "doc_count": 4, "min_price": { "value": 3320 } } ]
固然桶與桶之間也能夠進行嵌套,這樣就能知足複雜的聚合場景了。
例如,統計每一個商圈的房源價格分佈狀況:
-- SQL描述 SELECT bizcircleCode, GROUP_CONCAT(price) FROM rooms WHERE cityCode = 110000 GROUP BY bizcircleCode
桶聚合實現以下:
{ "aggs": { "bizcircle_price": { "terms": { "field": "bizcircleCode" }, "aggs": { "price_list": { "terms": { "field": "price" } } } } } }
聚合結果以下:
{ "bizcircle_price": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": 18335745, "doc_count": 1, "price_list": { "buckets": [ { "key": 3500, "doc_count": 1 } ] }, ... ... ] }
一般狀況下,聚合只返回了統計的一些指標,當須要獲取聚合後每組的文檔信息(小區的名字和座標等)時,該怎麼處理呢?這時,使用 top_hits 子句就能夠實現。
例如,獲取西二旗每一個小區最便宜的房源信息:
{ "aggs": { "rooms": { "top_hits": { "size": 1, "sort": { "price": "asc" }, "_source": [] } } } }
其中,size 爲組內返回的文檔個數,sort 表示組內文檔的排序規則,_source 指定組內文檔返回的字段。
聚合後的房源信息:
{ "bizcircle_price": { "buckets": [ { "key": 1111050770108, "doc_count": 1, "rooms": { "hits": { "total": 1, "hits": [ { "_index": "rooms", "_source": { "resblockId": 1111050770108, "resblockName": "領秀慧谷C區", "size": 15.3, "bizcircleName": [ "西二旗", "回龍觀" ], "location": "40.106349,116.31051", }, "sort": [ 3500 ] } ] } } }] } }
從 Elasticsearch 5.0 以後,增長了一個新特性 field collapsing(字段摺疊),字段摺疊就是特定字段進行合併並去重,而後返回結果集,該功也能實現 agg top_hits 的聚合效果。
例如, 增長文檔信息 部分的獲取西二旗每一個小區最便宜的房源信息,能夠實現爲:
{ "collapse": { "field": "resblockId", //按resblockId字段進行摺疊 "inner_hits": { "name": "top_price", //房源信息結果鍵名 "size": 1, //每一個摺合集文檔數 "sort": [ //每一個摺合集文檔排序規則 { "price": "desc" } ], "_source": [] //文檔的字段 } } }
檢索結果以下:
{ "hits": { "total": 7, "hits": [ { "_index": "rooms", "_score": 1, "_source": { "resblockId": 1111050770108, "resblockName": "領秀慧谷C區", ... ... }, "fields": { "resblockId": [ 1111050770108 ] }, "inner_hits": { "top_price": { "hits": { "total": 1, "hits": [ { "_index": "rooms", "_source": { "resblockId": 1111050770108, "resblockName": "領秀慧谷C區", "price": 3500, ... ... "location": "40.106349,116.31051" }, "sort": [ 3500 ] }] } } } ] } }
Field collapsing 和 agg top_hits 區別:field collapsing 的結果是夠精確,同時速度較快,更支持分頁功能。
Elasticsearch 一樣也支持了空間位置檢索,便可以經過地理座標點進行過濾檢索。
因爲地理座標點不能被動態映射自動檢測,須要顯式聲明對應字段類型爲 geo-point,以下:
PUT /rooms //索引名 { "mappings": { "restaurant": { "properties": { ... ... "location": { //空間位置檢索字段 "type": "geo_point" //字段類型 } } } } }
當需檢索字段類型設置成 geo_point 後,推送的經緯度信息的形式能夠是字符串、數組或者對象,以下:
形式 | 符號 | 示例 |
---|---|---|
字符串 | 「lat,lon」 | 「40.060937,116.315943」 |
對象 | lat 和 lon | { 「lat」:40.060937, 「lon」:116.315943 } |
數組 | [lon, lat] | [116.315943, 40.060937] |
特別須要注意數組形式時 lon 與 lat 的先後位置,否則就果斷踩坑了。
而後,推送含有經緯度的數據:
POST /rooms/room/ { "resblockId": 1321052240532, "resblockName": "領秀新硅谷1號院", "houseId": 1112046338679, "cityCode": 110000, "size": 14, "bizcircleCode": [ 611100314 ], "bizcircleName": [ "西二旗" ], "price": 3330, "location": "40.060937,116.315943" }
Elasticsearch 中支持 4 種地理座標點過濾器,以下表:
名稱 | 描述 |
---|---|
geo_distance | 找出與指定位置在給定距離內的點 |
geo_distance_range | 找出與指定點距離在最小距離和最大距離之間的點 |
geo_bounding_box | 找出落在指定矩形框中的點 |
geo_polygon | 找出落在多邊形中的點,將不說明 |
例如,查找西二旗地鐵站 4km 的房源信息:
{ "filter": { //過濾器 "geo_distance": { "distance": "4km", "location": { "lat": 40.106349, "lon": 116.31051 } } } }
LBS 檢索的結果爲:
{ "hits": [ { "_index": "rooms", "_source": { "resblockId": 1111050770108, "resblockName": "領秀慧谷C區", ... ... "location": "40.106349,116.31051" } }, { "_index": "rooms", "_source": { "resblockId": 1111047349969, "resblockName": "融澤嘉園", ... ... "location": "40.074203,116.315445" } } ] }
本文講述了使用 Elasticsearch 進行 聚合 和 LBS 檢索,儘管文中只是以示例形式進行說明,會存在不少不全面的地方,仍是但願對你我學習 Elasticsearch 能有所幫助。