如何编写Elasticsearch子聚合查询统计先试用后注册的用户数量
实现方案
前提说明:你需要确保所有行为事件文档都携带@timestamp时间戳字段,用于判断事件发生的先后顺序,否则无法准确判断「先试用后注册」的逻辑,只能统计「既产生过免费试用行为又完成注册」的用户数。
修正后完整查询
{ "size": 0, // 不返回原始文档,仅返回聚合结果节省资源 "aggs": { "origin": { "terms": { "field": "origin.visitor_id.keyword", // 修正为实际的访客唯一标识字段 "size": 10000 // 可根据实际用户量调整,数据量过大建议用partition参数分页遍历 }, "aggs": { // 聚合1:判断该用户最早的行为是否属于免费试用层级 "user_started_out_free": { "scripted_metric": { "init_script": "state.min_time = Long.MAX_VALUE; state.is_free = false", "map_script": """ long current_time = doc['@timestamp'].value.toInstant().toEpochMilli(); if (current_time < state.min_time) { state.min_time = current_time; state.is_free = doc['user.tenant_tier.keyword'].value == 'free'; } """, "combine_script": "return state.is_free ? 1 : 0", "reduce_script": "return states[0]" } }, // 聚合2:判断该用户是否存在注册成功事件 "then_registered": { "filter": { "term": { "event_key.keyword": "registration" // 对应你的事件标识字段 } } }, // 聚合3:判断是否同时满足「先试用后注册」条件 "is_trial_user_then_registered": { "bucket_script": { "buckets_path": { "user_started_out_free": "user_started_out_free.value", "registered_count": "then_registered._count" }, "script": "params.user_started_out_free == 1 && params.registered_count > 0 ? 1 : 0" } } } }, // 最终统计符合条件的用户总数 "num_trial_then_registered": { "sum_bucket": { "buckets_path": "origin>is_trial_user_then_registered" } } } }
关键逻辑说明
user_started_out_free脚本指标聚合:遍历同一个访客的所有事件,记录最早发生事件对应的租户层级,若为free则返回1,否则返回0then_registered过滤聚合:统计同一个访客下注册事件的数量,大于0代表该访客完成过注册is_trial_user_then_registered桶脚本聚合:同时满足「最早事件为免费层级」和「存在注册事件」则返回1,否则返回0- 最终的
num_trial_then_registered求和聚合:累加所有符合条件的访客数,就是目标统计结果
注意事项
- 如果你的ES版本较低不支持三引号多行脚本,可把脚本内容压缩为单行,转义特殊字符即可
- 如果访客量超过10万,不要直接把terms的size设置为过大值,建议使用terms聚合的
partition参数分批遍历所有访客,避免节点内存溢出 - 如果确实没有时间戳字段不需要判断顺序,可以把
user_started_out_free替换为过滤user.tenant_tier.keyword = free的filter聚合,判断doc_count>0即可
内容的提问来源于stack exchange,提问作者EinBischen3100
相关产品推荐
相关产品推荐

