Elasticsearch检索 — 聚合和LBS

原文:https://www.fanhaobai.com/2017/08/elasticsearch-advanced-search.html

文章 Elasticsearch检索实战 已经讲述了 Elasticsearch 基本检索使用,已满足大部分检索场景,但是某些特定项目中会使用到 聚合LBS 这类高级检索,以满足检索需求。这里将讲述 Elasticsearch 的聚合和 LBS 检索使用方法。

本文示例的房源数据,见这里,检索同样使用 Elasticsearch 的 DSL 对比 SQL 来说明。

聚合

常规聚合

aggs 子句聚合是 Elasticsearch 常规的聚合实现方式。

桶和指标

先理解这两个基本概念:

名称 描述
桶(Buckets) 满足特定条件的文档的集合
指标(Metrics) 对桶内的文档进行统计计算

每个聚合都是 一个或者多个桶和零个或者多个指标 的组合,聚合可能只有一个桶,可能只有一个指标,或者可能两个都有。例如这个 SQL:

SELECT COUNT(field_name) FROM table GROUP BY field_name

其中COUNT(field_name)相当于指标,GROUP BY field_name相当于桶。桶在概念上类似于 SQL 的分组(GROUP BY),而指标则类似于 COUNT() 、 SUM() 、 MAX() 等统计方法。

桶和指标的可用取值列表:

分类 操作符 描述
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 能有所帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,576评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,515评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,017评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,626评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,625评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,255评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,825评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,729评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,271评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,363评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,498评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,183评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,867评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,338评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,458评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,906评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,507评论 2 359

推荐阅读更多精彩内容

  • 原文:https://www.fanhaobai.com/2017/08/elasticsearch-search...
    Howborn阅读 2,256评论 0 5
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,697评论 18 139
  • 博客原文一博客原文二 翻译作品,水平有限,如有错误,烦请留言指正。原文请见 官网英文文档 起步 Elasticse...
    rabbitGYK阅读 3,261评论 0 68
  • Solr&ElasticSearch原理及应用 一、综述 搜索 http://baike.baidu.com/it...
    楼外楼V阅读 7,305评论 1 17
  • 引 “十二点了啊,这么晚,小成应该已经睡了吧”阿文最近不知道怎么了,总觉得家里面充满了不对劲,但是他又说不上来到底...
    说说侃侃聊聊阅读 246评论 0 1