Elasticsearch 实现sql group by sum

190 阅读2分钟

Elasticsearch 数据处理:group by sum操作

Elasticsearch 数据聚合实现类型 mysql中 group by sum。

et:

select sum(flow) from appflow group by opTime, userId
type AppFlow struct {
    OpTime  int64  `json:"opTime"`  // 操作时间
    UserId  string `json:"userId"`  // 用户id
    App     string `json:"app"`     // app
    Flow    int64  `json:"flow"`    // 流量
    Channel string `json:"channel"` // 渠道
    GroupUser string `json:"groupUser"` // 聚合字段
}
type UserFlow struct {
    OpTime  int64  `json:"opTime"`  // 操作时间
    UserId  string `json:"userId"`  // 用户id
    Flow    int64  `json:"flow"`    // 流量
}

Elasticsearch 中实现group by 需要使用 Aggregation et:

// userFlowGroup 聚合用户数据并返回
func (e *AppFlow) userFlowGroup() []*UserFlow {
    dtList := make([]*UserFlow, 0)
    boolQuery := elastic.NewBoolQuery()
    timeAgg := elastic.NewTermsAggregation().Field("opTime")
    userAgg := elastic.NewTermsAggregation().Field("userId")
    flowSumAgg := elastic.NewSumAggregation().Field("flow")
    
    userAgg.SubAggregation("flow_sum", flowSumAgg)
    timeAgg.SubAggregation("user", userAgg)
    searchResult, err := getESClient().Search().
        Index(e.Index()). // 设置索引名
        Query(boolQuery).
        Aggregation("times", timeAgg). // 设置聚合条件,并为聚合条件设置一个名字
        Size(0).                       // 不需要文档详情,只需要聚合结果
        Do(ctx)                        // 执行请求
    if err != nil {
        log.print("search err:%+v", err)
        return dtList
    }
    timeAggRes, _ := searchResult.Aggregations.Terms("times")
    for _, timeBucket := range timeAggRes.Buckets {
        userAggRes, _:= timeBucket.Aggregations.Terms("user")
        for _, userBucket := range userAggRes.Buckets{
            sentFlow, _ := schemeBucket.Aggregations.Sum("flow_sum")
            tmp:= &UserFlow{
                OpTime:fmt.Sprintf("%v", timeBucket.Key),
                UserId:fmt.Sprintf("%v", userBucket.Key),
                Flow: int64(*sentFlow.Value)
            }
            dtList = append(dtList, tmp)
        }
    }
    return dtList
}

在以上代码中:只是group by 2个字段,处理结果的时候就需要 嵌套for循环,这种嵌套for循环个人感觉非常不安全,所以我想了个办法,弄了个骚操作。

在插入数据的时候,根据group by 的字段生成一个groupUser 字段并存储到es中。这样就只需要一次循环一次聚合了

func (e *AppFlow) GroupId() []*UserFlow {
    e.GroupUser = fmt.Sprintf("%d_%s", e.OpTime, e.UserId) 
}
// userFlowGroup 聚合用户数据并返回
func (e *AppFlow) userFlowGroup() []*UserFlow {
    dtList := make([]*UserFlow, 0)
    boolQuery := elastic.NewBoolQuery()
    groupAgg := elastic.NewTermsAggregation().Field("groupUser")
    flowSumAgg := elastic.NewSumAggregation().Field("flow")
    
    groupAgg.SubAggregation("flow_sum", flowSumAgg)
    searchResult, err := getESClient().Search().
        Index(e.Index()). // 设置索引名
        Query(boolQuery).
        Aggregation("group", groupAgg). // 设置聚合条件,并为聚合条件设置一个名字
        Size(0).                       // 不需要文档详情,只需要聚合结果
        Do(ctx)                        // 执行请求
    if err != nil {
        log.print("search err:%+v", err)
        return dtList
    }
    groupAggRes, _ := searchResult.Aggregations.Terms("group")
    for _, groupData := range groupAggRes.Buckets {
        sentFlow, _ := schemeBucket.Aggregations.Sum("flow_sum")
        sL := strings.Split(groupData, "_")
        if len(sL) < 2 {
            continue
        }
        dt, _ := strconv.ParseInt(sL[0], 10, 0)
        tmp:= &UserFlow{
            OpTime:dt,
            UserId:sL[1],
            Flow: int64(*sentFlow.Value)
        }
        dtList = append(dtList, tmp)}
    }
    return dtList
}

使用中间的groupUser 增加了es使用空间,优化了查询,不知道诸位有没有更好的方式。。。。。总感觉这个骚操作有点不靠谱..。。。