原文:https://www.fanhaobai.com/2017/08/elasticsearch-advanced-search.html
文章 Elasticsearch檢索實戰(zhàn) 已經(jīng)講述了 Elasticsearch 基本檢索使用,已滿足大部分檢索場景枪向,但是某些特定項目中會使用到 聚合 和 LBS 這類高級檢索晕粪,以滿足檢索需求。這里將講述 Elasticsearch 的聚合和 LBS 檢索使用方法盆犁。
本文示例的房源數(shù)據(jù)抛人,見這里,檢索同樣使用 Elasticsearch 的 DSL 對比 SQL 來說明。
聚合
常規(guī)聚合
aggs 子句聚合是 Elasticsearch 常規(guī)的聚合實現(xiàn)方式省撑。
桶和指標
先理解這兩個基本概念:
名稱 | 描述 |
---|---|
桶(Buckets) | 滿足特定條件的文檔的集合 |
指標(Metrics) | 對桶內(nèi)的文檔進行統(tǒng)計計算 |
每個聚合都是 一個或者多個桶和零個或者多個指標 的組合,聚合可能只有一個桶俯在,可能只有一個指標竟秫,或者可能兩個都有。例如這個 SQL:
SELECT COUNT(field_name) FROM table GROUP BY field_name
其中COUNT(field_name)
相當于指標跷乐,GROUP BY field_name
相當于桶肥败。桶在概念上類似于 SQL 的分組(GROUP BY),而指標則類似于 COUNT() 愕提、 SUM() 馒稍、 MAX() 等統(tǒng)計方法。
桶和指標的可用取值列表:
分類 | 操作符 | 描述 |
---|---|---|
桶 | terms | 按精確值劃分桶 |
指標 | sum | 桶內(nèi)對該字段值求總數(shù) |
指標 | min | 桶內(nèi)對該字段值求最小值 |
指標 | max | 桶內(nèi)對該字段值求最大值 |
指標 | avg | 桶內(nèi)對該字段值求平均數(shù) |
指標 | cardinality( 基數(shù)) | 桶內(nèi)對該字段不同值的數(shù)量(distinct 值) |
簡單聚合
Elasticsearch 聚合 DSL 描述如下:
"aggs" : {
"aggs_name" : {
"operate" : { "field" : "field_name" }
}
}
其中浅侨,aggs_name 表示聚合結(jié)果返回的字段名纽谒,operate 表示桶或指標的操作符名,field_name 為需要進行聚合的字段如输。
- 例1鼓黔,統(tǒng)計西二旗每個小區(qū)的房源數(shù)量:
-- 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" }
}
}
}
聚合結(jié)果如下:
{
"hits": {
"total": 6,
"max_score": 1,
"hits": [... ...]
},
"aggregations": {
"resblock_list": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1321052240532, //小區(qū)id為1321052240532有4間房
"doc_count": 4
},
{
"key": 1111047349969,//小區(qū)id為1111047349969有1間房
"doc_count": 1
},
{
"key": 1111050770108,//小區(qū)id為1111050770108有1間房
"doc_count": 1
}
]
}
}}
可見,此時聚合的結(jié)果有且只有分組后文檔的 數(shù)量不见,只適合做一些分組后文檔數(shù)的統(tǒng)計澳化。
- 例2,去重統(tǒng)計西二旗小區(qū)的數(shù)量:
-- SQL描述
SELECT COUNT(DISTINCT resblockId) FROM rooms WHERE bizcircleCode = 611100314
使用 cardinality 指標統(tǒng)計:
{
"aggs": {
"resblock_count": {
"cardinality": {
"field": "resblockId"
}
}
}
}
添加度量指標
上述的簡單聚合脖祈,雖然可以統(tǒng)計桶內(nèi)的文檔數(shù)量肆捕,但是沒法實現(xiàn)組內(nèi)的其他指標統(tǒng)計刷晋,比如小區(qū)內(nèi)的最低房源價格盖高,這時就可以給桶添加一個 min 指標。
-- SQL描述
SELECT resblockId, MIN(price) FROM rooms WHERE bizcircleCode = 611100314
添加 min 指標后為:
{
"aggs": {
"resblock_list": {
"terms": { "field": "resblockId" },
"aggs": {
"min_price": {
"min": { "field": "price" }
}
}
}
}
}
結(jié)果為:
"buckets": [
{
"key": 1321052240532,
"doc_count": 4,
"min_price": {
"value": 3320
}
}
]
嵌套桶
當然桶與桶之間也可以進行嵌套眼虱,這樣就能滿足復雜的聚合場景了喻奥。
例如,統(tǒng)計每個商圈的房源價格分布情況:
-- SQL描述
SELECT bizcircleCode, GROUP_CONCAT(price) FROM rooms WHERE cityCode = 110000 GROUP BY bizcircleCode
桶聚合實現(xiàn)如下:
{
"aggs": {
"bizcircle_price": {
"terms": { "field": "bizcircleCode" },
"aggs": {
"price_list": {
"terms": { "field": "price" }
}
}
}
}
}
聚合結(jié)果如下:
{
"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
}
]
},
... ...
]
}
增加文檔信息
通常情況下捏悬,聚合只返回了統(tǒng)計的一些指標撞蚕,當需要獲取聚合后每組的文檔信息(小區(qū)的名字和坐標等)時,該怎么處理呢过牙?這時甥厦,使用 top_hits 子句就可以實現(xiàn)纺铭。
例如,獲取西二旗每個小區(qū)最便宜的房源信息:
{
"aggs": {
"rooms": {
"top_hits": {
"size": 1,
"sort": { "price": "asc" },
"_source": []
}
}
}
}
其中刀疙,size 為組內(nèi)返回的文檔個數(shù)舶赔,sort 表示組內(nèi)文檔的排序規(guī)則,_source 指定組內(nèi)文檔返回的字段谦秧。
聚合后的房源信息:
{
"bizcircle_price": {
"buckets": [
{
"key": 1111050770108,
"doc_count": 1,
"rooms": {
"hits": {
"total": 1,
"hits": [
{
"_index": "rooms",
"_source": {
"resblockId": 1111050770108,
"resblockName": "領秀慧谷C區(qū)",
"size": 15.3,
"bizcircleName": [ "西二旗", "回龍觀" ],
"location": "40.106349,116.31051",
},
"sort": [ 3500 ]
}
]
}
}
}]
}
}
字段折疊
從 Elasticsearch 5.0 之后竟纳,增加了一個新特性 field collapsing(字段折疊),字段折疊就是特定字段進行合并并去重疚鲤,然后返回結(jié)果集锥累,該功也能實現(xiàn) agg top_hits 的聚合效果。
例如集歇, 增加文檔信息 部分的獲取西二旗每個小區(qū)最便宜的房源信息桶略,可以實現(xiàn)為:
{
"collapse": {
"field": "resblockId", //按resblockId字段進行折疊
"inner_hits": {
"name": "top_price", //房源信息結(jié)果鍵名
"size": 1, //每個折合集文檔數(shù)
"sort": [ //每個折合集文檔排序規(guī)則
{ "price": "desc" }
],
"_source": [] //文檔的字段
}
}
}
檢索結(jié)果如下:
{
"hits": {
"total": 7,
"hits": [
{
"_index": "rooms",
"_score": 1,
"_source": {
"resblockId": 1111050770108,
"resblockName": "領秀慧谷C區(qū)",
... ...
},
"fields": {
"resblockId": [ 1111050770108 ]
},
"inner_hits": {
"top_price": {
"hits": {
"total": 1,
"hits": [
{
"_index": "rooms",
"_source": {
"resblockId": 1111050770108,
"resblockName": "領秀慧谷C區(qū)",
"price": 3500,
... ...
"location": "40.106349,116.31051"
},
"sort": [ 3500 ]
}]
}
}
}
]
}
}
Field collapsing 和 agg top_hits 區(qū)別:field collapsing 的結(jié)果是夠精確,同時速度較快鬼悠,更支持分頁功能删性。
LBS
Elasticsearch 同樣也支持了空間位置檢索,即可以通過地理坐標點進行過濾檢索焕窝。
索引格式
由于地理坐標點不能被動態(tài)映射自動檢測蹬挺,需要顯式聲明對應字段類型為 geo-point,如下:
PUT /rooms //索引名
{
"mappings": {
"restaurant": {
"properties": {
... ...
"location": { //空間位置檢索字段
"type": "geo_point" //字段類型
}
}
}
}
}
數(shù)據(jù)格式
當需檢索字段類型設置成 geo_point 后它掂,推送的經(jīng)緯度信息的形式可以是字符串巴帮、數(shù)組或者對象,如下:
形式 | 符號 | 示例 |
---|---|---|
字符串 | “l(fā)at,lon” | “40.060937,116.315943” |
對象 | lat 和 lon | { “l(fā)at”:40.060937, “l(fā)on”:116.315943 } |
數(shù)組 | [lon, lat] | [116.315943, 40.060937] |
特別需要注意數(shù)組形式時 lon 與 lat 的前后位置虐秋,不然就果斷踩坑了榕茧。
然后,推送含有經(jīng)緯度的數(shù)據(jù):
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 | 找出與指定位置在給定距離內(nèi)的點 |
geo_distance_range | 找出與指定點距離在最小距離和最大距離之間的點 |
geo_bounding_box | 找出落在指定矩形框中的點 |
geo_polygon | 找出落在多邊形中的點用押,將不說明 |
例如,查找西二旗地鐵站 4km 的房源信息:
{
"filter": { //過濾器
"geo_distance": {
"distance": "4km",
"location": {
"lat": 40.106349,
"lon": 116.31051
}
}
}
}
LBS 檢索的結(jié)果為:
{
"hits": [
{
"_index": "rooms",
"_source": {
"resblockId": 1111050770108,
"resblockName": "領秀慧谷C區(qū)",
... ...
"location": "40.106349,116.31051"
}
},
{
"_index": "rooms",
"_source": {
"resblockId": 1111047349969,
"resblockName": "融澤嘉園",
... ...
"location": "40.074203,116.315445"
}
}
]
}
總結(jié)
本文講述了使用 Elasticsearch 進行 聚合 和 LBS 檢索靶剑,盡管文中只是以示例形式進行說明蜻拨,會存在很多不全面的地方,還是希望對你我學習 Elasticsearch 能有所幫助桩引。