
本文详解如何在 go 中通过 mongodb 聚合管道(aggregation pipeline)对嵌套数组字段 atndnc 进行条件筛选与分组统计,动态计算每位学生的出勤百分比,并返回结构化结果。
本文详解如何在 go 中通过 mongodb 聚合管道(aggregation pipeline)对嵌套数组字段 atndnc 进行条件筛选与分组统计,动态计算每位学生的出勤百分比,并返回结构化结果。
在 Go 应用中对接 MongoDB 时,常需对嵌套数组(如学生考勤记录)执行条件聚合分析——例如:按学院、专业、学期、班级、课程及日期范围筛选多条考勤文档,再按学生学号(rollno)汇总其所有出勤记录,最终计算出勤率(%)。该需求本质是典型的「条件过滤 + 数组展开 + 分组聚合 + 比例计算」流程,需熟练运用 $match、$unwind、$group、$project 等聚合阶段。
以下是一个完整、可运行的聚合 pipeline 示例(基于官方推荐的 mongo-go-driver,非已停更的 mgo),适用于 MongoDB 4.0+:
import (
"context"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
// 定义输出结构体(便于反序列化)
type AttendanceReport struct {
RollNo string `bson:"_id"`
Name string `bson:"name"`
Percent float64 `bson:"prcntg"`
}
func GetStudentAttendancePercent(
collection *mongo.Collection,
collegeID, stream, semester, section, subject string,
startDate, endDate time.Time,
) ([]AttendanceReport, error) {
// 构建聚合 pipeline
pipeline := []bson.M{
// 步骤1:条件匹配(支持多字段 + 日期范围)
{
"$match": bson.M{
"college_id": collegeID,
"stream": stream,
"semester": semester,
"section": section,
"subject": subject,
"date": bson.M{
"$gte": startDate,
"$lte": endDate,
},
},
},
// 步骤2:展开 atndnc 数组(每个元素变为独立文档)
{"$unwind": "$atndnc"},
// 步骤3:按 rollno 和 name 分组,统计总课时数(即该生出现次数)和出勤次数
{
"$group": bson.M{
"_id": bson.M{
"rollno": "$atndnc.rollno",
"name": "$atndnc.name",
},
"totalClasses": bson.M{"$sum": 1},
"presentCount": bson.M{"$sum": bson.M{"$cond": []interface{}{bson.M{"$eq": []interface{}{"$atndnc.attend", true}}, 1, 0}}},
},
},
// 步骤4:投影计算百分比(保留两位小数),并扁平化字段
{
"$project": bson.M{
"_id": 0,
"rollno": "$_id.rollno",
"name": "$_id.name",
"prcntg": bson.M{
"$round": []interface{}{bson.M{"$multiply": []interface{}{bson.M{"$divide": []interface{}{"$presentCount", "$totalClasses"}}, 100}}, 2},
},
},
},
}
cursor, err := collection.Aggregate(context.TODO(), pipeline)
if err != nil {
return nil, err
}
defer cursor.Close(context.TODO())
var results []AttendanceReport
if err = cursor.All(context.TODO(), &results); err != nil {
return nil, err
}
return results, nil
}
✅ 关键说明与注意事项:
- $unwind 是必需步骤:因 atndnc 是数组,必须先展开才能对其中每个对象进行独立处理;若跳过此步,$group 将无法按 rollno 正确归类。
- $group 中 _id 必须为对象:如 bson.M{"rollno": "$atndnc.rollno", "name": "$atndnc.name"},确保同一学生不同姓名拼写不会被误拆(实际业务中建议以 rollno 唯一主键分组,name 仅作辅助展示)。
- 出勤率计算逻辑:使用 $cond + $sum 统计 attend: true 出现次数,再用 $divide 和 $multiply 换算为百分比,$round 控制精度。
- 日期范围查询:startDate/endDate 需传入 time.Time 类型,MongoDB 自动匹配 ISODate 字段;务必确保时区一致(建议统一用 UTC 存储与查询)。
-
性能提示:为提升查询效率,应在 college_id, stream, semester, section, subject, date 等常用过滤字段上建立复合索引,例如:
db.attendance.createIndex({ "college_id": 1, "stream": 1, "semester": 1, "section": 1, "subject": 1, "date": 1 })
通过上述方案,你将获得一个类型安全、逻辑清晰、易于维护的 Go 聚合查询实现,直接返回符合前端或报表需求的 []AttendanceReport 结构体切片,无需额外后处理。











