Elasticsearch檢索 — 聚合和LBS

原文: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 爲須要進行聚合的字段。排序

  • 例1,統計西二旗每一個小區的房源數量:
-- 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
        }
     ]
  }
}}

可見,此時聚合的結果有且只有分組後文檔的 數量,只適合作一些分組後文檔數的統計。

  • 例2,去重統計西二旗小區的數量:
-- 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 的結果是夠精確,同時速度較快,更支持分頁功能。

LBS

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 能有所幫助。

相關文章
相關標籤/搜索