如何在Elastic Search中获取按营收排名的前100位客人并返回完整对象及聚合求和结果
看起来你之前的思路有个关键偏差——你用total字段做terms聚合,这是在按单次消费金额分组,而不是按客人分组,会导致相同消费金额的不同客人预订被归到同一个桶里,显然不符合你「按客人总营收排名」的需求。
正确的做法是先按客人的唯一标识(比如客人邮箱、专属ID这类能唯一区分每个客人的字段)做terms聚合,然后在每个客人的桶里同时计算总营收(用sum聚合)和获取该客人的完整文档(用top_hits聚合)。
修正后的查询语句
假设你的客人唯一标识是contact.email(如果有单独的guest_id字段,用它会更可靠),这里需要用它的keyword子字段来做聚合(text类型字段无法直接用于聚合):
GET reservations/_search { "size": 0, "aggs": { "top_revenue_guests": { "terms": { "field": "contact.email.keyword", // 按客人唯一标识分组 "size": 100, "order": { "total_revenue": "desc" // 按总营收降序排名 } }, "aggs": { "total_revenue": { // 计算该客人的累计总营收 "sum": { "field": "total" } }, "guest_details": { // 获取该客人的完整文档(取最新1条即可) "top_hits": { "size": 1, "_source": { // 指定返回完整的客人对象字段 "includes": ["*"] } } } } } } }
预期返回结果
每个桶里会同时包含该客人的总营收、预订次数,以及完整的客人对象:
{ "aggregations": { "top_revenue_guests": { "buckets": [ { "key": "john@mail.com", // 客人的唯一标识 "doc_count": 3, // 该客人的预订次数 "total_revenue": { "value": 2450.5 // 该客人的累计总营收 }, "guest_details": { "hits": { "hits": [ { "_source": { "id": "1334620", "total": 110.8, "payment": "unpaid", "contact": { "name": "John Doe", "email": "john@mail.com" } } } ] } } }, // 其他客人的结果桶... ] } } }
关键说明
为什么sum聚合不能返回文档?
sum是纯数值计算类聚合,它的核心作用是统计数值总和,本身不具备返回完整文档的能力。想要获取客人的完整对象,必须用top_hits这类专门用于返回命中文档的聚合,把它和sum放在同一个terms桶里,就能同时拿到总营收和客人信息。关于客人唯一标识的选择
如果你的数据里有单独的guest_id字段,优先用它做terms聚合(比邮箱更稳定);如果没有,就选能唯一区分客人的字段组合(比如contact.name.keyword+contact.email.keyword,不过邮箱通常已经足够唯一)。top_hits的size设置
如果一个客人有多个预订文档,top_hits的size设为1就足够获取客人的基础信息(因为客人的联系方式等信息通常是一致的);如果需要返回该客人的所有预订记录,可以调整size值,但注意不要超过Elasticsearch的单查询返回限制。
内容的提问来源于stack exchange,提问作者Samball

