You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang中MongoDB多条件计数:替代多次调用coll.CountDocuments的聚合方案

问题:聚合查询替代多次CountDocuments返回空结果

我在Golang中通过多次调用coll.CountDocuments实现多条件文档计数,现在想把所有筛选条件整合进单次聚合查询来替代多次调用,但编写的聚合管道代码返回了空的游标(cur)和结果(result),求解决。

原实现代码(正常工作)

func NoOfDocumentsInfo(DB string, col string, filters ...bson.D) ([]int64, error) {
    if nil == dbInstance {
        if nil == GetDBInstance() {
            logger.Error("Not connecting to DB")
            err := errors.New("DB connection error")
            return nil, err
        }
    }

    logger.Debugf("%s %s", DB, col)

    coll := dbInstance.Database(DB).Collection(col)

    counts := make([]int64, len(filters))
    for i, filter := range filters {
        count, err := coll.CountDocuments(context.TODO(), filter)
        if err != nil {
            logger.Fatal(err)
            return nil, err
        }

        counts[i] = count
    }

    return counts, nil
}

尝试的聚合管道代码(返回空结果)

func NoOfDocumentsInfo(DB string, col string, filters ...bson.D) ([]int64, error) {
    if dbInstance == nil {
        if GetDBInstance() == nil {
            logger.Error("Not connecting to DB")
            err := errors.New("DB connection error")
            return nil, err
        }
    }

    logger.Debugf("%s %s", DB, col)

    coll := dbInstance.Database(DB).Collection(col)

    pipeline := make([]bson.M, 0, len(filters)+2)
    pipeline = append(pipeline, bson.M{"$match": bson.M{"$or": filters}})
    pipeline = append(pipeline, bson.M{"$group": bson.M{"_id": nil, "count": bson.M{"$sum": 1}}})
    pipeline = append(pipeline, bson.M{"$group": bson.M{"_id": nil, "count": bson.M{"$first": "$count"}}})

    var result struct {
        Count int64 `bson:"count"`
    }

    cur, err := coll.Aggregate(context.TODO(), pipeline)
    if err != nil {
        logger.Fatal(err)
        return nil, err
    }

    logger.Debugf("cur: %+v", cur)
    err = cur.Decode(&result)
    logger.Debugf("result: %+v, err: %v", result, err)
    if err != nil {
        logger.Fatal(err)
        return nil, err
    }

    return []int64{result.Count}, nil
}

问题分析与解决方案

原代码的核心问题

  1. 逻辑偏离需求:用$or把所有筛选条件合并后,统计的是所有条件匹配的文档总数,但原需求是分别统计每个筛选条件的匹配数,完全不是一回事。
  2. 冗余聚合阶段:第二次$group完全没必要,第一次$group已经能得到总数,多此一举反而可能引发异常。
  3. 游标处理错误:没有检查游标是否有数据就直接解码,如果聚合没有返回结果(比如没有文档匹配任何条件),Decode会直接报错。

正确实现:用$facet实现多条件并行统计

MongoDB的$facet阶段允许在单次聚合中同时运行多个子管道,正好满足"一次查询得到多个条件计数"的需求。以下是修正后的代码:

import "fmt"

func NoOfDocumentsInfo(DB string, col string, filters ...bson.D) ([]int64, error) {
    if dbInstance == nil {
        if GetDBInstance() == nil {
            logger.Error("Not connecting to DB")
            return nil, errors.New("DB connection error")
        }
    }

    logger.Debugf("%s %s", DB, col)
    coll := dbInstance.Database(DB).Collection(col)

    // 构建$facet的每个子管道,对应一个筛选条件的统计
    facet := bson.M{}
    for idx := range filters {
        // 为每个条件生成唯一的字段名
        facetKey := fmt.Sprintf("filter_%d", idx)
        facet[facetKey] = []bson.M{
            {"$match": filters[idx]}, // 应用当前筛选条件
            {"$count": "total"},       // 统计匹配文档数
        }
    }

    // 聚合管道只需要$facet阶段
    pipeline := []bson.M{{"$facet": facet}}

    var result bson.M
    cur, err := coll.Aggregate(context.TODO(), pipeline)
    if err != nil {
        logger.Fatal(err)
        return nil, err
    }
    defer cur.Close(context.TODO()) // 务必关闭游标,避免资源泄漏

    // 检查游标是否有结果
    if !cur.Next(context.TODO()) {
        if cur.Err() != nil {
            logger.Fatal(cur.Err())
            return nil, cur.Err()
        }
        // 无匹配文档时返回全0数组
        return make([]int64, len(filters)), nil
    }

    // 解码$facet的结果
    if err := cur.Decode(&result); err != nil {
        logger.Fatal(err)
        return nil, err
    }

    // 解析每个条件的统计结果
    counts := make([]int64, len(filters))
    for idx := range filters {
        facetKey := fmt.Sprintf("filter_%d", idx)
        // 取出对应子管道的结果数组
        if resultArr, ok := result[facetKey].([]interface{}); ok && len(resultArr) > 0 {
            // 数组第一个元素就是统计结果对象
            if countObj, ok := resultArr[0].(bson.M); ok {
                if total, ok := countObj["total"].(int64); ok {
                    counts[idx] = total
                }
            }
        }
        // 若某个条件无匹配,保持默认值0
    }

    return counts, nil
}

代码说明

  • $facet的作用:每个子管道独立运行,分别对应当初的一个filters条件,实现多条件并行统计,避免多次数据库调用。
  • 兼容性:返回值和原函数完全一致([]int64),无需修改调用方代码。
  • 错误处理:完善了游标关闭、空结果处理逻辑,避免资源泄漏和空指针错误。

内容的提问来源于stack exchange,提问作者Joytiprokash Roy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 13:35:19