在 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 的聚合实现选择了牺牲精度来提高速度);数据量大的情况下,聚合比较慢,影响搜索体验。
 
而新的的字段折叠的方式是怎么实现的的呢,有这些要点:

  1. 折叠+取 inner_hits 分两阶段执行(组合聚合的方式只有一个阶段),所以 top hits 永远是精确的。
  2. 字段折叠只在 top hits 层执行,不需要每次都在完整的结果集上对为每个折叠主键计算实际的 doc values 值,只对 top hits 这小部分数据操作就可以,和 term agg 相比要节省很多内存。
  3. 因为只在 top hits 上进行折叠,所以相比组合聚合的方式,速度要快很多。
  4. 折叠 top docs 不需要使用全局序列(global ordinals)来转换 string,相比 agg 这也节省了很多内存。
  5. 分页成为可能,和常规搜索一样,具有相同的局限,先获取 from+size 的内容,再合并。
  6. search_after 和 scroll 暂未实现,不过具备可行性。
  7. 折叠只影响搜索结果,不影响聚合,搜索结果的 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]}]}}}}]}
}

好了,字段折叠介绍就到这里。

转自:https://elasticsearch.cn/article/132

ES Field Collapsing 字段折叠使用详解相关推荐

  1. Elasticsearch Collapsing 字段折叠使用详解_字段折叠后数量结果Total不准确解决方法

    在 Elasticsearch 5.x 有一个字段折叠(Field Collapsing)的功能非常有用,在这里分享一下 前言 在电商商品中有分SKU和SPU sku是和商品的库存相关的一个概念.比如 ...

  2. elasticsearch 基础 —— Field Collapsing字段折叠

    允许根据字段值折叠搜索结果.通过按折叠键选择顶部排序文档来完成折叠.例如,下面的查询检索每个用户的最佳推文,并按喜欢的数量对它们进行排序. GET /twitter/_search {"qu ...

  3. 【转载】salesforce 零基础开发入门学习(四)多表关联下的SOQL以及表字段Data type详解...

    salesforce 零基础开发入门学习(四)多表关联下的SOQL以及表字段Data type详解 建立好的数据表在数据库中查看有很多方式,本人目前采用以下两种方式查看数据表. 1.采用schema ...

  4. php字段验证规则,详解ThinkPHP中自动验证及验证规则

    本篇文章详细介绍了thinkphp中自动验证及验证规则的方法,希望对学习thinkphp的朋友有帮助! 详解ThinkPHP中自动验证及验证规则 ThinkPHP 内置了数据对象的自动验证功能来完成模 ...

  5. 使用order by排序判断返回结果的列数,order by排序判断字段数原理详解

    「作者主页」:士别三日wyx order by排序猜解列数原理详解 一.order by的两种使用方式 1)按照字段名排序 2)按照索引排序 二.order by怎么判断字段数? 1)正常的排序 3) ...

  6. JDBC详解(四):操作BLOB类型字段(超详解)

    JDBC详解(四):操作BLOB类型字段 前言 一.MySQL BLOB类型 二.向数据表中插入大数据类型 三.修改数据表中的Blob类型字段 四.从数据表中读取大数据类型 前言 本博主将用CSDN记 ...

  7. mysql数据库字段变形_详解如何利用amoeba(变形虫)实现mysql数据库读写分离

    摘要:这篇MySQL栏目下的"详解如何利用amoeba(变形虫)实现mysql数据库读写分离",介绍的技术点是"MySQL数据库.数据库读写分离.amoeba.MySQL ...

  8. ES自定义评分机制:function_score查询详解

    一.function_score介绍 主要用于让用户自定义查询相关性得分,实现精细化控制评分的目的. 在ES的常规查询中,只有参与了匹配查询的字段才会参与记录的相关性得分score的计算.但很多时候我 ...

  9. salesforce 零基础开发入门学习(四)多表关联下的SOQL以及表字段Data type详解

    建立好的数据表在数据库中查看有很多方式,本人目前采用以下两种方式查看数据表. 1.采用schema Builder查看表结构以及多表之间的关联关系,可以登录后点击setup在左侧搜索框输入schema ...

最新文章

  1. 洛谷P2234 [HNOI2002]营业额统计(01Tire树)
  2. WP8中 ListBox x下拉刷新 以及 ScrollViewer/ListBox 的ManipulationCompleted 失效的解决方案
  3. freebsd 运维人员
  4. python 分离整数与小数_Python编程:离不开算术运算符的顺序结构
  5. php7.1 aes 加密解密,PHP7.1中AES加密解密方法 mcrypt_module_open()替换方案
  6. tomcat 开启远程debug
  7. 每天一小时python官方文档学习(三)————函数
  8. 2022年中国政企采购数字化转型白皮书
  9. 仿博客园个人博客(3)基本完成
  10. 如何清除计算机的u盘使用记录,如何消除电脑上U盘使用记录
  11. CAD2018下载AutoCAD2018下载安装教程附软件下载
  12. StikyNotes便签软件
  13. ConvLSTM-AE for VAD (ICME2017-SIST)
  14. win7在扩展屏/副屏显示任务栏
  15. 网页版视频怎么加快播放速度
  16. 专业能力一般的应届本科生,该如何准备面试
  17. java开源项目之IQQ学习记录之单例模式与log4j日志记录
  18. 快速建站 python_Django简单快捷建站(适合做大作业)
  19. T410s玩游戏很卡及其散热问题的解决
  20. java根据当前时间或指定时间获取前后几天或前后几个月或前后几年的时间

热门文章

  1. 云上贵州是这样建成的
  2. this code must be changed as there‘s no longer a status bar or status bar window.
  3. seq2seq结构的问题以及PGN网络模型
  4. 综合能源,matlab
  5. 【ABviewer从零开始教学查看器篇②】关于打开文件的快捷方式
  6. STM32系列芯片命名规则——简明
  7. 简单的bboss http组件使用方法
  8. poj 1080 Human Gene Functions (最长公共子序列变形)
  9. IDEA怎么在单独的窗口显示差异和并排两排显示代码?
  10. 5G开始赚钱了——全球5G进展分析