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 的聚合实现选择了牺牲精度来提高速度);数据量大的情况下,聚合比较慢,影响搜索体验。

 

而新的的字段折叠的方式是怎么实现的的呢,有这些要点:

  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

                  ]

                }

              ]

            }

          }

        }

      }

    ]

  }

}

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

                                                            <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 字段折叠的使用,广度搜索的更多相关文章

  1. Elasticsearch 5.x 字段折叠的使用

    在Elasticsearch 5.x  之前,如果实现一个数据折叠的功能是非常复杂的,随着5.X的更新,这一问题变得简单,找到了一遍技术文章,对这个问题描述的非常清楚,收藏下. 参考:https:// ...

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

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

  3. Elasticsearch系列---多字段搜索

    概要 本篇介绍一下multi_match的best_fields.most_fields和cross_fields三种语法的场景和简单示例. 最佳字段 bool查询采取"more-match ...

  4. Elasticsearch是一个分布式可扩展的实时搜索和分析引擎,elasticsearch安装配置及中文分词

    http://fuxiaopang.gitbooks.io/learnelasticsearch/content/  (中文) 在Elasticsearch中,文档术语一种类型(type),各种各样的 ...

  5. ES 13 - Elasticsearch的元字段 (_index、_type、_source、_routing等)

    目录 1 标识元字段 1.1 _index - 文档所属的索引 1.2 _uid - 包含_type和_id的复合字段 1.3 _type - 文档的类型 1.4 _id - 文档的id 2 文档来源 ...

  6. [转载] Activiti Tenant Id 字段释疑

    TENANT_ID_ :  这个字段表示租户ID.可以应对多租户的设计. 转载自: http://www.cnblogs.com/yg_zhang/p/4201288.html http://www. ...

  7. ElasticSearch多个字段分词查询高亮显示

    ElasticSearch关键字查询,将关键字分词后查询,多个字段,查询出来字段高亮显示. 查询方法如下: public List<NewsInfo> searcher2(String k ...

  8. 转载:Oracle常见字段类型

    转载节选自:https://bbs.csdn.net/topics/220059184 数据类型 参数 描述 char(n) n=1 to 2000字节 定长字符串,n字节长,如果不指定长度,缺省为1 ...

  9. Elasticsearch 单字符串多字段查询

    前言 有些时候,我们搜索的时候,只会提供一个输入框,但是会查询相关的多个字段,典型的如Google搜索,我们该如何用 Elasticsearch 如何实现呢? 实例 从单字符串查询的实例说起 创建测试 ...

随机推荐

  1. 如何在debug vue-cli建立的项目

    因为vue-cli使用了webpack,导致了一些页面报错了位置不精确的问题. 在网上找到了解决方案,在此分享. https://segmentfault.com/q/1010000008757714 ...

  2. STM32——TIM2定时器定时

    STM32 中一共有11 个定时器,其中2 个高级控制定时器,4 个普通定时器和2 个基本定时器,以及2 个看门狗定时器和1 个系统嘀嗒定时器.其中系统嘀嗒定时器是前文中所描述的SysTick,看门狗 ...

  3. BZOJ4083 : [Wf2014]Wire Crossing

    WF2014完结撒花~ 首先求出所有线段之间的交点,并在交点之间连边,得到一个平面图. 这个平面图不一定连通,故首先添加辅助线使其连通. 然后求出所有域,在相邻域之间连一条代价为$1$的边. 对起点和 ...

  4. BZOJ2828 : 火柴游戏

    设$f[i][j][k]$表示考虑了前$i$个数字,增加了$j$根火柴,删掉了$k$根火柴是否可能,用bitset加速DP. 然后设$g[i][j]$表示增加了$i$根火柴,删掉了$j$根火柴的最小代 ...

  5. 考前停课集训 Day3 匪

    Day3——作死不可活的一天 Day3 今天下午才考 晚上时间少 下午网每断 因此我是PY的 然后被抓了 成绩做0分处理. 是啊,我只会抄题解. 其他什么都不会. 一无是处. 真的. 真实能力:ran ...

  6. 劈配 [多省省选] [BZOJ5251] [网络流]

    题目链接 分析: 这道题看题都看了我好久... 我们可以容易想到这道题和网络流有关. 首先,从原点向每个学员连一条流量为1的边 然后,要限制每个导师的学员,在每个导师连到汇点的时候流量限制为bi 再接 ...

  7. Exception引起的性能问题

    先show一下两段代码,两段代码都能比较好的实现业务逻辑,但是在高并发下,如果传入的参数为空,那么两段代码的性能表现完全不一样. private static string Get(string fi ...

  8. NodeJS Web模块

    NodeJS Web模块 本文介绍nodeJS的http模块的基本用法,实现简单服务器和客户端 经典Web架构 Client:客户端一般指浏览器,通过HTTP协议向服务器发送请求(request) S ...

  9. NHibernate查询优化的相关资料

    一.http://www.cnblogs.com/dddd218/archive/2009/09/01/1557640.html 1.立即加载(lazy=false)并不能在所有情况下都会减少SQL语 ...

  10. “百度杯”CTF比赛 九月场---123

    右键查看源代码 然后构造user.php,显示空白,源码也是空白,既然上边说用户名,密码了,参考大佬的博客,放文件user.php.bak这是备份文件,一打开上边全是用户名,有戏,爆破 添加字典,也就 ...