【转载】Elasticsearch 5.x 字段折叠的使用,广度搜索
https://elasticsearch.cn/article/132
备注,分组字段只能是 keyword或num类型,不能是text类型
在 Elasticsearch 5.x 有一个字段折叠(Field Collapsing,#22337)的功能非常有意思,在这里分享一下,
字段折叠是一个很有历史的需求了,可以看这个 issue,编号#256,最初是2010年7月提的issue,也是讨论最多的帖子之一(240+评论),熬了6年才支持的特性,你说牛不牛,哈哈。
目测该特性将于5.3发布,尝鲜地址:Elasticsearch-5.3.0-SNAPSHOT,文档地址:search-request-collapse。
So,什么是字段折叠,可以理解就是按特定字段进行合并去重,比如我们有一个菜谱搜索,我希望按菜谱的“菜系”字段进行折叠,即返回结果每个菜系都返回一个结果,也就是按菜系去重,我搜索关键字“鱼”,要去返回的结果里面各种菜系都有,有湘菜,有粤菜,有中餐,有西餐,别全是湘菜,就是这个意思,通过按特定字段折叠之后,来丰富搜索结果的多样性。
说到这里,有人肯定会想到,使用 term agg+ top hits agg 来实现啊,这种组合两种聚和的方式可以实现上面的功能,不过也有一些局限性,比如,不能分页,#4915;结果不够精确(top term+top hits,es 的聚合实现选择了牺牲精度来提高速度);数据量大的情况下,聚合比较慢,影响搜索体验。
而新的的字段折叠的方式是怎么实现的的呢,有这些要点:
- 折叠+取 inner_hits 分两阶段执行(组合聚合的方式只有一个阶段),所以 top hits 永远是精确的。
- 字段折叠只在 top hits 层执行,不需要每次都在完整的结果集上对为每个折叠主键计算实际的 doc values 值,只对 top hits 这小部分数据操作就可以,和 term agg 相比要节省很多内存。
- 因为只在 top hits 上进行折叠,所以相比组合聚合的方式,速度要快很多。
- 折叠 top docs 不需要使用全局序列(global ordinals)来转换 string,相比 agg 这也节省了很多内存。
- 分页成为可能,和常规搜索一样,具有相同的局限,先获取 from+size 的内容,再合并。
- search_after 和 scroll 暂未实现,不过具备可行性。
- 折叠只影响搜索结果,不影响聚合,搜索结果的 total 是所有的命中纪录数,去重的结果数未知(无法计算)。
下面来看看具体的例子,就知道怎么回事了,使用起来很简单。
- 先准备索引和数据,这里以菜谱为例,name:菜谱名,type 为菜系,rating 为用户的累积平均评分
DELETE recipes
PUT recipes
POST recipes/type/_mapping
{
"properties": {
"name":{
"type": "text"
},
"rating":{
"type": "float"
},"type":{
"type": "keyword"
}
}
}
POST recipes/type/
{
"name":"清蒸鱼头","rating":1,"type":"湘菜"
}
POST recipes/type/
{
"name":"剁椒鱼头","rating":2,"type":"湘菜"
}
POST recipes/type/
{
"name":"红烧鲫鱼","rating":3,"type":"湘菜"
}
POST recipes/type/
{
"name":"鲫鱼汤(辣)","rating":3,"type":"湘菜"
}
POST recipes/type/
{
"name":"鲫鱼汤(微辣)","rating":4,"type":"湘菜"
}
POST recipes/type/
{
"name":"鲫鱼汤(变态辣)","rating":5,"type":"湘菜"
}
POST recipes/type/
{
"name":"广式鲫鱼汤","rating":5,"type":"粤菜"
}
POST recipes/type/
{
"name":"鱼香肉丝","rating":2,"type":"川菜"
}
POST recipes/type/
{
"name":"奶油鲍鱼汤","rating":2,"type":"西菜"
}
- 现在我们看看普通的查询效果是怎么样的,搜索关键字带“鱼”的菜,返回3条数据
POST recipes/type/_search
{
"query": {"match": {
"name": "鱼"
}},"size": 3
}
全是湘菜,我的天,最近上火不想吃辣,这个第一页的结果对我来说就是垃圾,如下:
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 9,
"max_score": 0.26742277,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHYF_OA-dG63Txsd",
"_score": 0.26742277,
"_source": {
"name": "鲫鱼汤(变态辣)",
"rating": 5,
"type": "湘菜"
}
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHXO_OA-dG63Txsa",
"_score": 0.19100356,
"_source": {
"name": "红烧鲫鱼",
"rating": 3,
"type": "湘菜"
}
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHWy_OA-dG63TxsZ",
"_score": 0.19100356,
"_source": {
"name": "剁椒鱼头",
"rating": 2,
"type": "湘菜"
}
}
]
}
}
我们再看看,这次我想加个评分排序,大家都喜欢的是那些,看看有没有喜欢吃的,执行查询:
POST recipes/type/_search
{
"query": {"match": {
"name": "鱼"
}},"sort": [
{
"rating": {
"order": "desc"
}
}
],"size": 3
}
结果稍微好点了,不过3个里面2个是湘菜,还是有点不合适,结果如下:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 9,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHYF_OA-dG63Txsd",
"_score": null,
"_source": {
"name": "鲫鱼汤(变态辣)",
"rating": 5,
"type": "湘菜"
},
"sort": [
5
]
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHYW_OA-dG63Txse",
"_score": null,
"_source": {
"name": "广式鲫鱼汤",
"rating": 5,
"type": "粤菜"
},
"sort": [
5
]
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHX7_OA-dG63Txsc",
"_score": null,
"_source": {
"name": "鲫鱼汤(微辣)",
"rating": 4,
"type": "湘菜"
},
"sort": [
4
]
}
]
}
}
现在我知道了,我要看看其他菜系,这家不是还有西餐、广东菜等各种菜系的么,来来,帮我每个菜系来一个菜看看,换 terms agg 先得到唯一的 term 的 bucket,再组合 top_hits agg,返回按评分排序的第一个 top hits,有点复杂,没关系,看下面的查询就知道了:
GET recipes/type/_search
{
"query": {
"match": {
"name": "鱼"
}
},
"sort": [
{
"rating": {
"order": "desc"
}
}
],"aggs": {
"type": {
"terms": {
"field": "type",
"size": 10
},"aggs": {
"rated": {
"top_hits": {
"sort": [{
"rating": {"order": "desc"}
}],
"size": 1
}
}
}
}
},
"size": 0,
"from": 0
}
看下面的结果,虽然 json 结构有点复杂,不过总算是我们想要的结果了,湘菜、粤菜、川菜、西菜都出来了,每样一个,不重样:
{
"took": 4,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 9,
"max_score": 0,
"hits": []
},
"aggregations": {
"type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "湘菜",
"doc_count": 6,
"rated": {
"hits": {
"total": 6,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHYF_OA-dG63Txsd",
"_score": null,
"_source": {
"name": "鲫鱼汤(变态辣)",
"rating": 5,
"type": "湘菜"
},
"sort": [
5
]
}
]
}
}
},
{
"key": "川菜",
"doc_count": 1,
"rated": {
"hits": {
"total": 1,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHYr_OA-dG63Txsf",
"_score": null,
"_source": {
"name": "鱼香肉丝",
"rating": 2,
"type": "川菜"
},
"sort": [
2
]
}
]
}
}
},
{
"key": "粤菜",
"doc_count": 1,
"rated": {
"hits": {
"total": 1,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHYW_OA-dG63Txse",
"_score": null,
"_source": {
"name": "广式鲫鱼汤",
"rating": 5,
"type": "粤菜"
},
"sort": [
5
]
}
]
}
}
},
{
"key": "西菜",
"doc_count": 1,
"rated": {
"hits": {
"total": 1,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHY3_OA-dG63Txsg",
"_score": null,
"_source": {
"name": "奶油鲍鱼汤",
"rating": 2,
"type": "西菜"
},
"sort": [
2
]
}
]
}
}
}
]
}
}
}
上面的实现方法,前面已经说了,可以做,有局限性,那看看新的字段折叠法如何做到呢,查询如下,加一个 collapse 参数,指定对那个字段去重就行了,这里当然对菜系“type”字段进行去重了:
GET recipes/type/_search
{
"query": {
"match": {
"name": "鱼"
}
},
"collapse": {
"field": "type"
},
"size": 3,
"from": 0
}
结果很理想嘛,命中结果还是熟悉的那个味道(和查询结果长的一样嘛),如下:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 9,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRJ_OA-dG63TxpW",
"_score": 0.018980097,
"_source": {
"name": "鲫鱼汤(微辣)",
"rating": 4,
"type": "湘菜"
},
"fields": {
"type": [
"湘菜"
]
}
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRk_OA-dG63TxpZ",
"_score": 0.013813315,
"_source": {
"name": "鱼香肉丝",
"rating": 2,
"type": "川菜"
},
"fields": {
"type": [
"川菜"
]
}
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRb_OA-dG63TxpY",
"_score": 0.0125863515,
"_source": {
"name": "广式鲫鱼汤",
"rating": 5,
"type": "粤菜"
},
"fields": {
"type": [
"粤菜"
]
}
}
]
}
}
我再试试翻页,把 from 改一下,现在返回了3条数据,from 改成3,新的查询如下:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 9,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRw_OA-dG63Txpa",
"_score": 0.012546891,
"_source": {
"name": "奶油鲍鱼汤",
"rating": 2,
"type": "西菜"
},
"fields": {
"type": [
"西菜"
]
}
}
]
}
}
上面的结果只有一条了,去重之后本来就只有4条数据,上面的工作正常,每个菜系只有一个菜啊,那我不乐意了,帮我每个菜系里面多返回几条,我好选菜啊,加上参数 inner_hits 来控制返回的条数,这里返回2条,按 rating 也排个序,新的查询构造如下:
GET recipes/type/_search
{
"query": {
"match": {
"name": "鱼"
}
},
"collapse": {
"field": "type",
"inner_hits": {
"name": "top_rated",
"size": 2,
"sort": [
{
"rating": "desc"
}
]
}
},
"sort": [
{
"rating": {
"order": "desc"
}
}
],
"size": 2,
"from": 0
}
查询结果如下,完美:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 9,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHYF_OA-dG63Txsd",
"_score": null,
"_source": {
"name": "鲫鱼汤(变态辣)",
"rating": 5,
"type": "湘菜"
},
"fields": {
"type": [
"湘菜"
]
},
"sort": [
5
],
"inner_hits": {
"top_rated": {
"hits": {
"total": 6,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHYF_OA-dG63Txsd",
"_score": null,
"_source": {
"name": "鲫鱼汤(变态辣)",
"rating": 5,
"type": "湘菜"
},
"sort": [
5
]
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHX7_OA-dG63Txsc",
"_score": null,
"_source": {
"name": "鲫鱼汤(微辣)",
"rating": 4,
"type": "湘菜"
},
"sort": [
4
]
}
]
}
}
}
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHYW_OA-dG63Txse",
"_score": null,
"_source": {
"name": "广式鲫鱼汤",
"rating": 5,
"type": "粤菜"
},
"fields": {
"type": [
"粤菜"
]
},
"sort": [
5
],
"inner_hits": {
"top_rated": {
"hits": {
"total": 1,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoESHYW_OA-dG63Txse",
"_score": null,
"_source": {
"name": "广式鲫鱼汤",
"rating": 5,
"type": "粤菜"
},
"sort": [
5
]
}
]
}
}
}
}
]
}
}
好了,字段折叠介绍就到这里。
<div class="aw-upload-img-list">
</div>
<ul class="aw-upload-file-list">
</ul>
<hr>
<div style="font-size: 12px; margin-top:10px; color: #c9cccf;">
[尊重社区原创,转载请保留或注明出处]<br>
本文地址:http://elasticsearch.cn/article/132 </div>
<hr>
<div class="aw-mod aw-topic-bar" style="margin: 10px 0px 0px;" id="question_topic_editor" data-type="article" data-id="132">
<div class="tag-bar clearfix">
<span class="topic-tag" data-id="849">
<a class="text" href="https://elasticsearch.cn/topic/5.3">
<i class="icon icon-tag"></i>
5.3 </a>
</span>
<span class="topic-tag" data-id="850">
<a class="text" href="https://elasticsearch.cn/topic/Field+Collapsing">
<i class="icon icon-tag"></i>
Field Collapsing </a>
</span>
<span class="topic-tag" data-id="851">
<a class="text" href="https://elasticsearch.cn/topic/%E5%AD%97%E6%AE%B5%E6%8A%98%E5%8F%A0">
<i class="icon icon-tag"></i>
字段折叠 </a>
</span>
</div>
</div>
<hr>
</div>
【转载】Elasticsearch 5.x 字段折叠的使用,广度搜索的更多相关文章
- Elasticsearch 5.x 字段折叠的使用
在Elasticsearch 5.x 之前,如果实现一个数据折叠的功能是非常复杂的,随着5.X的更新,这一问题变得简单,找到了一遍技术文章,对这个问题描述的非常清楚,收藏下. 参考:https:// ...
- elasticsearch 基础 —— Field Collapsing字段折叠
允许根据字段值折叠搜索结果.通过按折叠键选择顶部排序文档来完成折叠.例如,下面的查询检索每个用户的最佳推文,并按喜欢的数量对它们进行排序. GET /twitter/_search { "q ...
- Elasticsearch系列---多字段搜索
概要 本篇介绍一下multi_match的best_fields.most_fields和cross_fields三种语法的场景和简单示例. 最佳字段 bool查询采取"more-match ...
- Elasticsearch是一个分布式可扩展的实时搜索和分析引擎,elasticsearch安装配置及中文分词
http://fuxiaopang.gitbooks.io/learnelasticsearch/content/ (中文) 在Elasticsearch中,文档术语一种类型(type),各种各样的 ...
- ES 13 - Elasticsearch的元字段 (_index、_type、_source、_routing等)
目录 1 标识元字段 1.1 _index - 文档所属的索引 1.2 _uid - 包含_type和_id的复合字段 1.3 _type - 文档的类型 1.4 _id - 文档的id 2 文档来源 ...
- [转载] Activiti Tenant Id 字段释疑
TENANT_ID_ : 这个字段表示租户ID.可以应对多租户的设计. 转载自: http://www.cnblogs.com/yg_zhang/p/4201288.html http://www. ...
- ElasticSearch多个字段分词查询高亮显示
ElasticSearch关键字查询,将关键字分词后查询,多个字段,查询出来字段高亮显示. 查询方法如下: public List<NewsInfo> searcher2(String k ...
- 转载:Oracle常见字段类型
转载节选自:https://bbs.csdn.net/topics/220059184 数据类型 参数 描述 char(n) n=1 to 2000字节 定长字符串,n字节长,如果不指定长度,缺省为1 ...
- Elasticsearch 单字符串多字段查询
前言 有些时候,我们搜索的时候,只会提供一个输入框,但是会查询相关的多个字段,典型的如Google搜索,我们该如何用 Elasticsearch 如何实现呢? 实例 从单字符串查询的实例说起 创建测试 ...
随机推荐
- 如何在debug vue-cli建立的项目
因为vue-cli使用了webpack,导致了一些页面报错了位置不精确的问题. 在网上找到了解决方案,在此分享. https://segmentfault.com/q/1010000008757714 ...
- STM32——TIM2定时器定时
STM32 中一共有11 个定时器,其中2 个高级控制定时器,4 个普通定时器和2 个基本定时器,以及2 个看门狗定时器和1 个系统嘀嗒定时器.其中系统嘀嗒定时器是前文中所描述的SysTick,看门狗 ...
- BZOJ4083 : [Wf2014]Wire Crossing
WF2014完结撒花~ 首先求出所有线段之间的交点,并在交点之间连边,得到一个平面图. 这个平面图不一定连通,故首先添加辅助线使其连通. 然后求出所有域,在相邻域之间连一条代价为$1$的边. 对起点和 ...
- BZOJ2828 : 火柴游戏
设$f[i][j][k]$表示考虑了前$i$个数字,增加了$j$根火柴,删掉了$k$根火柴是否可能,用bitset加速DP. 然后设$g[i][j]$表示增加了$i$根火柴,删掉了$j$根火柴的最小代 ...
- 考前停课集训 Day3 匪
Day3——作死不可活的一天 Day3 今天下午才考 晚上时间少 下午网每断 因此我是PY的 然后被抓了 成绩做0分处理. 是啊,我只会抄题解. 其他什么都不会. 一无是处. 真的. 真实能力:ran ...
- 劈配 [多省省选] [BZOJ5251] [网络流]
题目链接 分析: 这道题看题都看了我好久... 我们可以容易想到这道题和网络流有关. 首先,从原点向每个学员连一条流量为1的边 然后,要限制每个导师的学员,在每个导师连到汇点的时候流量限制为bi 再接 ...
- Exception引起的性能问题
先show一下两段代码,两段代码都能比较好的实现业务逻辑,但是在高并发下,如果传入的参数为空,那么两段代码的性能表现完全不一样. private static string Get(string fi ...
- NodeJS Web模块
NodeJS Web模块 本文介绍nodeJS的http模块的基本用法,实现简单服务器和客户端 经典Web架构 Client:客户端一般指浏览器,通过HTTP协议向服务器发送请求(request) S ...
- NHibernate查询优化的相关资料
一.http://www.cnblogs.com/dddd218/archive/2009/09/01/1557640.html 1.立即加载(lazy=false)并不能在所有情况下都会减少SQL语 ...
- “百度杯”CTF比赛 九月场---123
右键查看源代码 然后构造user.php,显示空白,源码也是空白,既然上边说用户名,密码了,参考大佬的博客,放文件user.php.bak这是备份文件,一打开上边全是用户名,有戏,爆破 添加字典,也就 ...